設計模式 #2 (工廠模式)

凌丹妙耀發表於2020-09-14

設計模式 #2 (工廠模式)


文章中所有工程程式碼和UML建模檔案都在我的這個GitHub的公開庫--->DesignPatternStar來一個好嗎?秋梨膏!


簡述 :提供一種建立物件的最佳方式。

在工廠模式中,我們在建立物件時不會對客戶端暴露建立邏輯,並且是通過使用一個共同的介面來指向新建立的物件。

面向介面(抽象)程式設計?聞到內味了嗎?7大設計原則中的依賴倒置原則迪米特法則介面隔離原則

當然不止用到了一個原則,一般設計模式都是多個設計原則的集合體。

簡單工廠模式

簡述:建立產品介面,需要產品時,利用工廠進行建立即可。

# 反例 :

public class negtive {
/*===============服務端======================*/
    interface Food{
        void eat();
    }

    static class Noodles implements Food{
        @Override
        public void eat() {
            System.out.println("吃麵條。。。。。");
        }
    }

/*=================客戶端===================*/
    public static void main(String[] args) {
        Food food01 = new Noodles();
        food01.eat();
    }
}

UML類圖如下:

image-2020019529

這時候,產品來改需求來了,“哥,你先把刀放下。我們們現在這 Noodles改名了,得改個特牛逼的名字Spaghetti,讓使用者記住我們們這是西餐義大利麵。”

這時候,因為你原有設計是上面的反例,你得能從修改服務端的原始碼開始,再修改客戶端原始碼。以後再有改名這類事,你還要把刀拿出來放桌上給產品看。

這種設計過於脆弱,因為這樣服務端原始碼和客戶端原始碼是耦合的,改變會牽一髮而動全身。

# 正例:

public class postive {

    /*===============服務端======================*/
    interface Food{
        void eat();
    }

    static class Spaghetti implements Food {
        @Override
        public void eat() {
            System.out.println("吃西餐麵條。。。。。");
        }
    }

    static class FoodFactory {
        public Food getFood(int num){
            Food food =null;
            switch (num){
                case 1 :
                    food = new Spaghetti();
            }
            return food;
        }
    }

    /*=================客戶端===================*/
    public static void main(String[] args) {
        FoodFactory foodFactory = new FoodFactory();
        Food food01 = foodFactory.getFood(1);
        food01.eat();
    }
}

UML類圖如下:

image-20200914191446305

通過這樣一個正例,把建立物件的程式碼全交給服務端處理,將服務端程式碼和客戶端程式碼進行了解耦。以後產品再找你聊天是不是可以暫時把刀收起來了?

這樣做的好處,不只是服務端開發人員受益,當服務端程式碼修改時,客戶端也不知道,也不需要知道。

這樣的設計模式並不是十全十美的,任何一種設計模式都不會是十全十美的。只是根據業務邏輯在各方面進行取捨。

簡單工廠模式的缺點

  • 客戶必須記住工廠中常量和具體產品的對映關係。
  • 一旦產品品種體量增大到一定程度,工廠類將變得非常臃腫。
  • 最致命的缺陷,增加產品時,就要修改工廠類。違反開閉原則

工廠方法模式

簡述:為了進行擴充套件,不違反開閉原則。

這裡是基於簡單工廠模式進行改進。

# 正例:

public class postive {
    /*===============服務端======================*/
    //-----------------------產品--------------------
    interface Food{
        void eat();
    }

    static class Spaghetti implements Food {
        @Override
        public void eat() {
            System.out.println("吃西餐麵條。。。。。");
        }
    }

    //新增產品
    static class Rice implements Food {
        @Override
        public void eat() {
            System.out.println("吃米飯。。。。。");
        }
    }

    //--------------------------工廠-----------------------
     interface FoodFactory {
         Food getFood();
    }

    static class SpaghettiFactory implements FoodFactory{

        @Override
        public Food getFood() {
            return new Spaghetti();
        }
    }

    //新增產品工廠
    static class RiceFactory implements FoodFactory{

        @Override
        public Food getFood() {
            return new Rice();
        }
    }

    /*=================客戶端===================*/
    public static void main(String[] args) {
         FoodFactory foodFactory = new  SpaghettiFactory();
         Food food01 = foodFactory.getFood();
        food01.eat();
    }
}

UML類圖如下:

針對簡單工廠違反開閉原則的這一缺陷,工廠方法模式進行優化。可以看到此時再去增加產品,不再需要修改工廠類,而是增加相應的產品類和工廠類即可。這是符合開閉原則的。

這裡就會有聰明的小問號有很多朋友了:

  1. 如果原始碼作者修改相關工廠類的類名,那這時候呼叫工廠類的客戶端程式碼就需要修改了,這不如簡單工廠呢?

首先這裡要明確一個概念,工廠類在實際使用中,是相當於介面類的,介面類一般不允許進行修改(非必須),工廠類作者有責任,有義務保證工廠類的類名是穩定的,也就是說,工廠類是比產品類更加穩定的。

  1. 既然使我們後面自己擴充套件的Rice類,為什麼不直接例項化它,直接使用。我們就是作者,為什麼不能直接使用?

這裡需要擴充套件一下,有時候一個產品類並不是孤立的,它和其他類一起組成一個服務框架。

下面增加一些類:

	/*===============服務端======================*/	  
	//------------------------產品質檢流程-----------------------、

    static class QualityInspection {

        public void checking(FoodFactory foodFactory){
            System.out.println("我是人肉質檢員。。。。。準備開吃 -_- ");
            Food food = foodFactory.getFood();
            food.eat();
        }
    }
    /*=================客戶端===================*/
    public static void main(String[] args) {
        FoodFactory foodFactory01 = new  SpaghettiFactory();
        FoodFactory foodFactory02 = new  RiceFactory();

        QualityInspection inspection = new QualityInspection();
        inspection.checking(foodFactory02);
        inspection.checking(foodFactory01);

image-20200914175016710

UML類圖如下:

image-20200914204539759

這時候,如果Rice沒有他的工廠類,甚至都沒辦法參加質檢,那還怎麼賣?

所以編寫工廠類並不只是單純為了例項化某些產品類,而是能讓配套服務通過工廠介面,得以呼叫工廠建立產品例項。

有的小朋友大大的眼睛裡還有疑惑:那為什麼QualityInspectionchecking方法不直接呼叫Food介面再進行產品的例項化呢?

這時候回到簡單工廠模式,產品類不同於工廠類,它是善變的,它會隨著需求的變化而變化,這時候,直接依賴產品類的各種方法,將需要被修改,違反開閉原則。這是死路,小朋友別槓了。哈哈哈。

當然,工廠方法模式也是有缺陷的:

  • 當業務需要的型別變多,目前只有食物,當產生飲料,日用品等類別時,我們又要建立新的工廠來實現,造成程式碼重複臃腫。

抽象工廠模式

針對工廠方法模式的缺陷,抽象工廠模式將進行改進,一個工廠負責建立一個產品簇的物件。

關於產品簇:是指多個存在內在聯絡的或者存在邏輯關係的產品。

image-20200914221314906

簡述:在抽象工廠模式中,介面是負責建立一個相關物件的工廠,不需要顯式指定它們的類。每個生成的工廠都能按照工廠模式提供物件。

抽象工廠模式(Abstract Factory Pattern)是圍繞一個超級工廠建立其他工廠。該超級工廠又稱為其他工廠的工廠。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。

# 正例:

public class postive {
    /*===============服務端======================*/
    
    //-----------------------產品--------------------
    /*----------------螺絲---------------------*/
    interface Screw{
        void createScrew();
    }

    static class Screw_06 implements Screw {
        @Override
        public void createScrew() {
            System.out.println("create Screw_06 666666。。。。。");
        }
    }

    static class Screw_08 implements Screw {
        @Override
        public void createScrew() {
            System.out.println("create Screw_08 8888888。。。。。");
        }
    }
    /*----------------螺母---------------------*/
    interface Nut{
        void createNut();
    }

    static class Nut_06 implements Nut {
        @Override
        public void createNut() {
            System.out.println("create Nut_06 666666。。。。。");
        }
    }

    static class Nut_08 implements Nut {
        @Override
        public void createNut() {
            System.out.println("create Nut_08 8888888。。。。。");
        }
    }
    
    
    //--------------------------工廠-----------------------
    interface ComponentsFactory {
        Screw getScrew();
        Nut getNut();
    }
    /*----------------6號工廠---------------------*/
    static class Factory_666 implements ComponentsFactory {

        @Override
        public Screw getScrew() {
            return new Screw_06();
        }

        @Override
        public Nut getNut() {
            return new Nut_06();
        }
    }

    /*----------------8號工廠---------------------*/
    static class Factory_888 implements ComponentsFactory {

        @Override
        public Screw getScrew() {
            return new Screw_08();
        }

        @Override
        public Nut getNut() {
            return new Nut_08();
        }
    }

    //------------------------產品質檢流程-----------------------、
    static class QualityInspection {

        public void checking(ComponentsFactory Factory){
            System.out.println("我是人肉質檢員。。。。。等待產出零件 -_- ");
            Screw screw = Factory.getScrew();
            Nut nut = Factory.getNut();
            screw.createScrew();
            nut.createNut();
            System.out.println("開始質檢.......");
            System.out.println("      ");
        }
    }
    
    /*=================客戶端===================*/
    public static void main(String[] args) {
        ComponentsFactory Factory01 = new   Factory_666();
        ComponentsFactory Factory02 = new   Factory_888();

        QualityInspection inspection = new QualityInspection();
        inspection.checking(Factory01);
        inspection.checking(Factory02);
    }
}

UML類圖如下:

image-20200914225519685

可以看到,如果在需要進行一種N號螺絲或者螺母的擴充套件,只需要增加一個實現N號螺絲或者螺母介面的產品類,利用一個新增N號工廠進行建立即可。

可以看到,抽象工廠仍然保持著簡單工廠模式和工廠方法模式的優點:

  • 服務端程式碼和客戶端程式碼是低耦合的。(簡單工廠模式)
  • 所有這一切動作都是新增,不是修改,符合開閉原則

還新增了一個特有的優點

  • 抽象工廠有效減少了工廠的數量,一個工廠就生產同一個產品簇的產品。

這下產品來改需求,是不是還可以笑嘻嘻跟他聊會天了?

再次強調,一個抽象工廠負責建立同一個產品簇的物件。而產品簇是指多個存在內在聯絡的或者存在邏輯關係的產品。也就是6號工廠只生產6號的零部件,不負責生產8號零部件。不能不同產品簇的產品混合到一個工廠中進行生產。

缺陷:當增加產品簇時(增加68號螺帽的生產),這時候就要修改以前工廠(68號工廠)的原始碼了。


總結就是:

  • 當產品簇比較固定時,考慮使用抽象工廠。
  • 當產品簇經常變動時,不建議使用抽象工廠。

相關文章