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

Ethan_Wong發表於2022-03-24

設計模式中的工廠模式(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幾乎所有工廠類

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

參考資料

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

《重學設計模式》

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

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

相關文章