設計模式學習筆記(三)簡單工廠、工廠方法和抽象工廠之間的區別

設計模式中的工廠模式(Factory Design pattern)是一個比較常用的創建型設計模式,其中可以細分為三種:簡單工廠(Simple Factory)、工廠方法(Factory Method)和抽象工廠(Abstract Factory)。那麼三者有什麼區別呢?先說結論:

  • 簡單工廠:只有唯一工廠(簡單工廠),一個產品介面/抽象類,根據簡單工廠中的靜態方法來創建具體產品對象。適用於產品較少,幾乎不擴展的情景
  • 工廠方法:有多個工廠(抽象工廠+多個具體工廠),一個產品介面/抽象類,根據繼承抽象工廠中的方法來多態創建具體產品對象。適用於一個類型的多個產品
  • 抽象方法:有多個工廠(抽象工廠+多個具體工廠),多個產品介面/抽象類,對產品子類進行分組,根據繼承抽象工廠中的方法多態創建同組的不同具體產品對象。適用於多個類型的多個產品

下面具體展開說明

一、簡單工廠模式(Simple Factory Pattern)

1.1 簡單工廠模式介紹

簡單工廠模式又叫做靜態工廠方法模式(static Factory Method pattern),它是通過使用靜態方法接收不同的參數來返回不同的實例對象。我們通過一個類圖來進行講解:

  • Product介面:定義要創建的產品對象的介面
  • ProductAProductBProductC產品類:實現產品介面,具有產品介面特性的具體產品
  • SimpleFactory簡單工廠:只有一個工廠,通過靜態方法createProduct創建具體的產品對象
  • client客戶端:客戶端有多個,每個客戶端可以通過簡單工廠來創建具體的產品對象

1.2 簡單工廠模式實現

我們以上面類圖為例,實現簡單工廠模式:

/**產品介面**/
public interface Product {

    void doSomething();
}

/**具體產品實現**/
class ProductA implements Product{

    @Override
    public void doSomething() {
        System.out.println("我是ProductA");
    }
}

class ProductB implements Product{

    @Override
    public void doSomething() {
        System.out.println("我是ProductB");
    }
}

class ProductC implements Product{

    @Override
    public void doSomething() {
        System.out.println("我是ProductC");
    }
}
/**簡單工廠**/
public class SimpleFactory {
    /**工廠類創建產品靜態方法**/
    public static Product createProduct(String productName) {
        Product instance = null;
        switch (productName){
            case "A":
                instance = new ProductA();
                break;
            case "B":
                instance = new ProductB();
                break;
            case "C":
                instance = new ProductC();
        }
        return instance;
    }
    /**客戶端(client)調用工廠類**/
    public static void main(String[] args) {
        SimpleFactory simpleFactory = new SimpleFactory();
        createProduct("A").doSomething();
        createProduct("B").doSomething();
    }
}
  • 優點:簡單工廠可以使客戶端免除直接創建對象的職責,能夠根據需要創建出對應的產品。實現客戶端和產品類程式碼分離。此外可以通過配置文件來實現不修改客戶端程式碼的情況下添加新的具體產品類(改進)。
  • 缺點:違背開閉原則,如果需要新增其他產品類,就必須在工廠類中新增if-else邏輯判斷(可以通過配置文件來改進)。但是整體來說,系統擴展還是相對其他工廠模式要困難。

綜上來說,簡單工廠模式適用於業務簡單,產品固定不會經常改變工廠類的情況。

1.3 簡單工廠模式使用場景

下面來看看簡單工廠模式一般用於哪些業務場景

1.3.1 JDK 、Spring等各類源碼

在Java 中就有這樣的設計,比如DateFormat中的這個方法就是簡單工廠的應用

private static DateFormat get(LocaleProviderAdapter adapter, int timeStyle, int dateStyle, Locale loc) {
    DateFormatProvider provider = adapter.getDateFormatProvider();
    DateFormat dateFormat;
    //邏輯判斷實現那個具體對象
    if (timeStyle == -1) {
        dateFormat = provider.getDateInstance(dateStyle, loc);
    } else {
        if (dateStyle == -1) {
            dateFormat = provider.getTimeInstance(timeStyle, loc);
        } else {
            dateFormat = provider.getDateTimeInstance(dateStyle, timeStyle, loc);
        }
    }
    return dateFormat;
}

此外還有Calender等,在Spring 源碼中也可以看到一些以”Factory”結尾的類,這些都是工廠模式的使用。

1.3.2 資料庫連接池

比如在業務連接資料庫時,需要支援不同的資料庫,比如有dbcpc3p0druid等等,這個時候資料庫連接方式有限,而且比較固定不容易更改,所以可以嘗試採用簡單工廠模式來進行管理資料庫連接對象。

二、工廠方法模式(Factory Method Pattern)

我們知道簡單工廠模式有違背開閉原則,不容易擴展的缺點,所以在 GOF 23種設計模式中也沒有簡單工廠模式,下面我們就來看看另外一種工廠模式:工廠方法模式

2.1 工廠方法模式介紹

抽象工廠模式所要解決的問題是在一個產品族上,若存在多個不同類型的產品情況下,介面選擇的問題。

工廠方法模式實際上是簡單工廠模式的升級,工廠方法模式定義除了產品介面外,還定義了一個用於創建對象工廠的介面,讓工廠子類再去實例化對應的產品類。通過類圖來解釋:

  • Product介面:和簡單工廠相同,提供產品對象的介面
  • ProductAProductBproductC:具體類型的產品對象
  • FactoryAFactoryBFactoryC:具體的產品工廠,實現具體的產品對象
  • AbstractFactory:抽象工廠,可以有多個,其中的方法負責返回創建的產品對象
  • Client:使用該模式的客戶端

2.2 工廠方法模式實現

對照著上面的類圖,我們可以對應實現相應的程式碼:

/**產品介面**/
public interface Product {

    void doSomething();
}

/**具體產品實現**/
class ProductA implements Product{

    @Override
    public void doSomething() {
        System.out.println("我是ProductA");
    }
}

class ProductB implements Product{

    @Override
    public void doSomething() {
        System.out.println("我是ProductB");
    }
}

class ProductC implements Product{

    @Override
    public void doSomething() {
        System.out.println("我是ProductC");
    }
}

/**工廠介面**/
public interface AbstractFactory {
	/**創建Product方法,區別與工廠模式的靜態方法**/
    public Product createProduct();
}

/**具體工廠實現**/
class FactoryA implements AbstractFactory{

    @Override
    public Product createProduct() {
        return new ProductA();
    }
}

class FactoryA implements AbstractFactory{

    @Override
    public Product createProduct() {
        return new ProductA();
    }
}

class FactoryA implements AbstractFactory{

    @Override
    public Product createProduct() {
        return new ProductA();
    }
}
/**客戶端調用工廠**/
public class Client {
    public static void main(String[] args) {
        Product productA = new FactoryA().createProduct();
        productA.doSomething();
        Product productB = new FactoryB().createProduct();
        productB.doSomething();
    }
}

其中最主要的是 AbstractFactory類中的createProduct方法,通過這個方法來生成具體產品,這也是為什麼叫工廠方法的原因。和簡單工廠的靜態方法不同,這裡是使用的非靜態調用方式。而且可以發現,沒有了簡單工廠中的 if-else邏輯判斷,相對而言擴展性也要強的多。

  • 優點:完全實現開閉原則,實現了可擴展和更複雜的層次結構。明確了職責,具有多態性,適用於任何實體類。
  • 缺點:如果業務增加,會使得系統中類的個數成倍增加,提高了程式碼的複雜度

2.3 工廠方法模式使用場景

2.3.1 Slf4j

在Slf4j 這個我們經常使用的日誌框架中,就有工廠方法模式的應用,比如使用頻率很高的獲取logger對象實例中:

private Logger logger = LoggerFactory.getLogger(Client.class);

點進源碼看我們會發現這個getLogger方法:

//簡單工廠模式
public static Logger getLogger(String name) {
    /**工廠方法模式的使用**/
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}
//工廠介面
public interface ILoggerFactory {
    Logger getLogger(String var1);
}
//Logger產品介面
public interface Logger {
    String ROOT_LOGGER_NAME = "ROOT";
    ...
}

需要調用工廠方法介面來實現具體logger 對象實例,這就是一個工廠方法模式的一個典型應用

2.3.2 一些規則配置解析

在一些需要不同類型的規則配置解析時,我們也可以用到工廠方法模式,比如引用《設計模式之美》的程式碼:

public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);

    IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileExtension);
    if (parserFactory == null) {
      throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);
    }
    IRuleConfigParser parser = parserFactory.createParser();

    String configText = "";
    //從ruleConfigFilePath文件中讀取配置文本到configText中
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }

  private String getFileExtension(String filePath) {
    //...解析文件名獲取擴展名,比如rule.json,返回json
    return "json";
  }
}

//因為工廠類只包含方法,不包含成員變數,完全可以復用,
//不需要每次都創建新的工廠類對象,所以,簡單工廠模式的第二種實現思路更加合適。
public class RuleConfigParserFactoryMap { //工廠的工廠
  private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>();

  static {
    cachedFactories.put("json", new JsonRuleConfigParserFactory());
    cachedFactories.put("xml", new XmlRuleConfigParserFactory());
    cachedFactories.put("yaml", new YamlRuleConfigParserFactory());
    cachedFactories.put("properties", new PropertiesRuleConfigParserFactory());
  }

  public static IRuleConfigParserFactory getParserFactory(String type) {
    if (type == null || type.isEmpty()) {
      return null;
    }
    IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase());
    return parserFactory;
  }
}

在需要添加新的規則配置解析器時,只需要創建新的 parser 類和 parserfactory 完成不同的配置

三、抽象工廠模式(Abastract Factory Pattern)

抽象工廠模式沒有簡單工廠和工廠方法模式那麼常用,場景比較特殊,在簡單工廠和工廠方法中,對於類只有一種分類方式,比如簡單工廠中,根據產品類型分為ProductAProductBProductC。但是如果有多種分類方式,比如按照產品的生產商分類,ProductA可能和ProductC為一類。這樣就用到了抽象工廠模式

3.1 抽象工廠模式介紹

抽象工廠模式(Abstract Factory Pattern)屬於創建型模式,它實際上是對工廠方法模式的擴展,相當於一個超級工廠,用於創建其他工廠的模式。在抽象工廠模式中,介面是負責創建一個相關對象的工廠,而且每個工廠都能按照工廠模式提供對象。其實抽象工廠也是為了減少工廠方法中的子類和工廠類數量,基於此提出的設計模式,如下圖(來源淘系技術):

比如在工廠方法中,我們只能按照鍵盤、主機、顯示器分別進行分類,這樣會造成大量的工廠類和產品子類。而抽象工廠可以將上述三種產品類進行分組,可以大大減少工廠類的數量。我們再來看看對應的類圖:

  • Product1Product2:定義一種類型的產品對象介面
  • Product1AProduct1B等:各種類型的具體產品對象
  • FactoryAFactoryB:具體產品工廠,負責創建該工廠類型下的產品對象
  • AbstractFactory:抽象工廠介面,定義一類產品對象
  • Client:客戶端,使用抽象工廠,調用產品對象

3.2 抽象工廠模式實現

下面就根據上面的類圖,利用程式碼實現抽象工廠:

/**Product1類的產品介面**/
public interface Product1 {
    void doSomething();
}

class Product1A implements Product1{

    @Override
    public void doSomething() {
        System.out.println("我是Product1A");
    }
}

class Product1B implements Product1{

    @Override
    public void doSomething() {
        System.out.println("我是Product1B");
    }
}


/** Product2類的產品介面**/
public interface Product2 {
    void doSomething();
}

class Product2A implements Product1{

    @Override
    public void doSomething() {
        System.out.println("我是Product2A");
    }
}

class Product2B implements Product1{

    @Override
    public void doSomething() {
        System.out.println("我是Product2B");
    }
}

/**抽象工廠介面**/
public interface AbstractFactory {

    public Product1 createProduct1();

    public Product2 createProduct2();
}

/**A類工廠**/
public class FactoryA implements AbstractFactory{

    @Override
    public Product1 createProduct1() {
        return new Product1A();
    }

    @Override
    public Product2 createProduct2() {
        return new Product2A();
    }
}

/**B類工廠**/
public class FactoryB implements AbstractFactory{

    @Override
    public Product1 createProduct1() {
        return new Product1B();
    }

    @Override
    public Product2 createProduct2() {
        return new Product2B();
    }
}


/**Client客戶端調用**/
public class Client {
    public static void main(String[] args) {
        new FactoryA().createProduct1().doSomething();
        new FactoryB().createProduct2().doSomething();
    }
}
  • 優點:增加分組比較容易,而且能大大減少工廠類的數量
  • 缺點:因為分組,所以分組中的產品擴展就比較困難,比如再新增一個Product3,就需要改動AbstractFactoryFactoryAFactoryB幾乎所有工廠類

綜上,沒有哪種方法是萬金油,要針對業務場景來使用哪種工廠模式

參考資料

//www.zhihu.com/question/27125796/answer/1615074467

《重學設計模式》

//www.cnblogs.com/sunweiye/p/10815928.html

//time.geekbang.org/column/article/197254