天天看點

設計模式學習總結——工廠模式

原創 房輝(懷興) 淘系技術  2020-12-02

解決的問題

用戶端在調用時不想判斷來執行個體化哪一個類或者執行個體化的過程過于複雜。

在工廠模式中,具體的實作類建立過程對用戶端是透明的,用戶端不決定具體執行個體化哪一個類,而是交由“工廠”來執行個體化。

簡單工廠

▐  結構

定義一個建立對象的接口,讓其子類自己決定執行個體化哪一個工廠類。

設計模式學習總結——工廠模式
  • 抽象類或接口:定義了要建立的産品對象的接口。
  • 具體實作:具有統一父類的具體類型的産品。
  • 産品工廠:負責建立産品對象。工廠模式同樣展現了開閉原則,将“建立具體的産品實作類”這部分變化的代碼從不變化的代碼“使用産品”中分離出來,之後想要新增産品時,隻需要擴充工廠的實作即可。

▐  使用

建立不同品牌的鍵盤

public interface Keyboard {
    void print();
    void input(Context context);
}

class HPKeyboard implements Keyboard {

    @Override
    public void print() {
        //...輸出邏輯;
    }

    @Override
    public void input(Context context) {
        //...輸入邏輯;
    }

}

class DellKeyboard implements Keyboard {

    @Override
    public void print() {
        //...輸出邏輯;
    }

    @Override
    public void input(Context context) {
        //...輸入邏輯;
    }

}

class LenovoKeyboard implements Keyboard {

    @Override
    public void print() {
        //...輸出邏輯;
    }

    @Override
    public void input(Context context) {
        //...輸入邏輯;
    }

}

/**
 * 工廠
 */
public class KeyboardFactory {
    public Keyboard getInstance(int brand) {
        if(BrandEnum.HP.getCode() == brand){
            return new HPKeyboard();
        } else if(BrandEnum.LENOVO.getCode() == brand){
            return new LenovoKeyboard();
        } else if(BrandEnum.DELL.getCode() == brand){
            return new DellKeyboard();
        }
        return null;
    }

    public static void main(String[] args) {
        KeyboardFactory keyboardFactory = new KeyboardFactory();
        Keyboard lenovoKeyboard = KeyboardFactory.getInstance(BrandEnum.LENOVO.getCode());
        //...
    }

}      

▐  缺陷

上面的工廠實作是一個具體的類KeyboardFactory,而非接口或者抽象類,getInstance()方法利用if-else建立并傳回具體的鍵盤執行個體,如果增加新的鍵盤子類,鍵盤工廠的建立方法中就要增加新的if-else。這種做法擴充性差,違背了開閉原則,也影響了可讀性。是以,這種方式使用在業務較簡單,工廠類不會經常更改的情況。

工廠方法

為了解決上面提到的"增加if-else"的問題,可以為每一個鍵盤子類建立一個對應的工廠子類,這些工廠子類實作同一個抽象工廠接口。這樣,建立不同品牌的鍵盤,隻需要實作不同的工廠子類。當有新品牌加入時,建立具體工廠繼承抽象工廠,而不用修改任何一個類。

設計模式學習總結——工廠模式
  • 抽象工廠:聲明了工廠方法的接口。
  • 具體産品工廠:實作工廠方法的接口,負責建立産品對象。
  • 産品抽象類或接口:定義工廠方法所建立的産品對象的接口。
  • 具體産品實作:具有統一父類的具體類型的産品。

public interface IKeyboardFactory {
    Keyboard getInstance();
}

public class HPKeyboardFactory implements IKeyboardFactory {
    @Override
    public Keyboard getInstance(){
        return new HPKeyboard();
    }
}

public class LenovoFactory implements IKeyboardFactory {
    @Override
    public Keyboard getInstance(){
        return new LenovoKeyboard();
    }
}

public class DellKeyboardFactory implements IKeyboardFactory {
    @Override
    public Keyboard getInstance(){
        return new DellKeyboard();
    }
}      

▐  缺點

每一種品牌對應一個工廠子類,在建立具體鍵盤對象時,執行個體化不同的工廠子類。但是,如果業務涉及的子類越來越多,難道每一個子類都要對應一個工廠類嗎?這樣會使得系統中類的個數成倍增加,增加了代碼的複雜度。

抽象工廠

為了縮減工廠實作子類的數量,不必給每一個産品配置設定一個工廠類,可以将産品進行分組,每組中的不同産品由同一個工廠類的不同方法來建立。

例如,鍵盤、主機這2種産品可以分到同一個分組——電腦,而不同品牌的電腦由不同的制造商工廠來建立。

設計模式學習總結——工廠模式

類似這種把産品類分組,組内不同産品由同一工廠類的不同方法實作的設計模式,就是抽象工廠模式。

抽象工廠适用于以下情況:

1. 一個系統要獨立于它的産品的建立、組合和表示時;

2. 一個系統要由多個産品系列中的一個來配置時;

3. 要強調一系列相關的産品對象的設計以便進行聯合使用時;

4. 當你提供一個産品類庫,而隻想顯示它們的接口而不是實作時;

設計模式學習總結——工廠模式
  • 抽象工廠:聲明了建立抽象産品對象的操作接口。
  • 具體産品工廠:實作了抽象工廠的接口,負責建立産品對象。
  • 産品抽象類或接口:定義一類産品對象的接口。
  • 具體産品實作:定義一個将被相應具體工廠建立的産品對象。

public interface Keyboard {
   void print();
}
public class DellKeyboard implements Keyboard {
    @Override
    public void print() {
        //...dell...dell;
    }
}
public class HPKeyboard implements Keyboard {
    @Override
    public void print() {
        //...HP...HP;
    }
}
public interface Monitor {
   void play();
}
public class DellMonitor implements Monitor {
    @Override
    public void play() {
        //...dell...dell;
    }
}
public class HPMonitor implements Monitor {
    @Override
    public void play() {
        //...HP...HP;
    }
}
public interface MainFrame {
   void run();
}
public class DellMainFrame implements MainFrame {
    @Override
    public void run() {
        //...dell...dell;
    }
}
public class HPMainFrame implements MainFrame {
    @Override
    public void run() {
        //...HP...HP;
    }
}
//工廠類。工廠分為Dell工廠和HP工廠,各自負責品牌内産品的建立
public interface IFactory {
    MainFrame createMainFrame();
    Monitor createMainFrame();
    Keyboard createKeyboard();
}
public class DellFactory implements IFactory {
      @Override
      public MainFrame createMainFrame(){
                MainFrame mainFrame = new DellMainFrame();
             //...造一個Dell主機;
             return mainFrame;
      }

      @Override
      public Monitor createMonitor(){
                Monitor monitor = new DellMonitor();
             //...造一個Dell顯示器;
             return monitor;
      }

      @Override
      public Keyboard createKeyboard(){
                Keyboard keyboard = new DellKeyboard();
             //...造一個Dell鍵盤;
             return Keyboard;
      }
}
public class HPFactory implements IFactory {
      @Override
      public MainFrame createMainFrame(){
                MainFrame mainFrame = new HPMainFrame();
             //...造一個HP主機;
             return mainFrame;
      }

      @Override
      public Monitor createMonitor(){
                Monitor monitor = new HPMonitor();
             //...造一個HP顯示器;
             return monitor;
      }

      @Override
      public Keyboard createKeyboard(){
                Keyboard keyboard = new HPKeyboard();
             //...造一個HP鍵盤;
             return Keyboard;
      }
}
//用戶端代碼。執行個體化不同的工廠子類,可以通過不同的建立方法建立不同的産品
public class Main {
    public static void main(String[] args) {
        IFactory dellFactory = new DellFactory();
        IFactory HPFactory = new HPFactory();
        //建立戴爾鍵盤
        Keyboard dellKeyboard = dellFactory.createKeyboard();
        //...
    }
}      

▐  優缺點

增加分組非常簡單,例如要增加Lenovo分組,隻需建立Lenovo工廠和具體的産品實作類。分組中的産品擴充非常困難,要增加一個滑鼠Mouse,既要建立抽象的Mouse接口, 又要增加具體的實作:DellMouse、HPMouse, 還要再每個Factory中定義建立滑鼠的方法實作。

▐  總結

  • 簡單工廠:唯一工廠類,一個産品抽象類,工廠類的建立方法依據入參判斷并建立具體産品對象。
  • 工廠方法:多個工廠類,一個産品抽象類,利用多态建立不同的産品對象,避免了大量的if-else判斷。
  • 抽象工廠:多個工廠類,多個産品抽象類,産品子類分組,同一個工廠實作類建立同組中的不同産品,減少了工廠子類的數量。

在下述情況下可以考慮使用工廠模式:

  1. 在編碼時不能預見需要建立哪種類的執行個體。
  2. 系統不應依賴于産品類執行個體如何被建立、組合和表達的細節。

總之,工廠模式就是為了友善建立同一接口定義的具有複雜參數和初始化步驟的不同對象。工廠模式一般用來建立複雜對象。隻需用new就可以建立成功的簡單對象,無需使用工廠模式,否則會增加系統的複雜度。

此外,如果對象的參數是不固定的,推薦使用Builder模式。

後記

在實際項目中,結合Spring中的InitializingBean接口,可以利用@Autowired注解優雅的實作工廠。