1. 程式人生 > >Java 設計模式(二):工廠方法模式

Java 設計模式(二):工廠方法模式

參考連結:工廠方法模式-Factory Method Pattern

在介紹簡單工廠模式時提到簡單工廠模式存在一個很嚴重的問題,就是當系統中需要引入新產品時,如果靜態工廠方法是通過傳入引數的不同來建立不同的產品,這必定要修改工廠類的原始碼,將違背“開閉原則”,如何實現增加新產品而不影響已有程式碼?工廠方法模式應運而生,本文將介紹第二種工廠模式——工廠方法模式。

1. 模式概述

定義:定義一個用於建立物件的介面,讓子類決定將哪一個類例項化。工廠方法模式讓一個類的例項化延遲到其子類。

工廠方法模式提供一個抽象工廠介面來宣告抽象工廠方法,而由其子類來具體實現工廠方法,建立具體的產品物件。工廠方法模式結構如圖所示:


在工廠方法模式結構圖中包含如下幾個角色:

  • Product(抽象產品):它是定義產品的介面,是工廠方法模式所建立物件的超類。
  • ConcreteProduct(具體產品):實現抽象產品介面,某種型別的具體產品由專門的具體工廠建立,具體工廠和具體產品之間一一對應。
  • Factory(抽象工廠):抽象工廠是工廠方法模式的核心,是具體工廠的父類,宣告工廠方法。
  • ConcreteFactory(具體工廠):它是抽象工廠類的子類,實現抽象工廠中定義的工廠方法,並可由客戶端呼叫,返回一個具體產品類的例項。

與簡單工廠模式相比,工廠方法模式最重要的區別是引入了抽象工廠角色,抽象工廠可以是介面,也可以是抽象類或者具體類,其典型程式碼如下所示:

public abstract class AbstractFactory {

    public abstract Shape getShape();
}

具體產品物件的建立由其子類負責,客戶端針對抽象工廠程式設計,可在執行時再指定具體工廠類,具體工廠類實現工廠方法,不同的具體工廠可以建立不同的具體產品,其典型程式碼如下所示:

public class CircleFactory extends AbstractFactory {

    /**
     * 返回具體的 Circle 例項
     * @return
     */
    @Override
    public
Shape getShape() { return new Circle(); } }
public class RectangleFactory extends AbstractFactory {

    /**
     * 返回具體的 Rectangle 例項
     * @return
     */
    @Override
    public Shape getShape() {
        return new Rectangle();
    }
}
public class SquareFactory extends AbstractFactory {

    /**
     * 返回具體的 Square 例項
     * @return
     */
    @Override
    public Shape getShape() {
        return new Square();
    }
}

在實際使用時,具體工廠類在實現工廠方法時除了建立具體產品物件之外,還可以負責產品物件的初始化工作以及一些資源和環境配置工作,例如連線資料庫、建立檔案等。

在客戶端程式碼中,只需關心工廠類即可,不同的具體工廠可以建立不同的產品,典型的客戶端類程式碼片段如下所示:

public class Client {

    public static void main(String[] args) {
        AbstractFactory factory;
        factory = new CircleFactory();
        Shape circle = factory.getShape();
        circle.draw();
    }
}

2. 方案改進

可以通過配置檔案來儲存具體工廠類ConcreteFactory的類名,更換新的具體工廠時無須修改原始碼,系統擴充套件更為方便。

在客戶端程式碼中將不再使用new關鍵字來建立工廠物件,而是將具體工廠類的類名儲存在配置檔案(如XML檔案)中,通過讀取配置檔案獲取類名字串,再使用Java的反射機制,根據類名字串生成物件。

在配置檔案config.xml用於儲存具體日誌記錄器工廠類類名:

<?xml version="1.0"?>
<config>
  <!-- 具體工廠類的全限定名 包名+類名 -->
  <className>RectangleFactory</className>
</config>

建立XMLUtil類讀取該配置檔案並通過儲存在其中的類名字串反射生成物件,其詳細程式碼如下:

public class XMLUtil {

    public static Object getFactory() {
        try {
            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = dFactory.newDocumentBuilder();

            Document doc;
            doc = builder.parse(new File("config.xml"));

            NodeList nl = doc.getElementsByTagName("className");
            Node classNode = nl.item(0).getFirstChild();

            String className = classNode.getNodeValue();
            Class clazz = Class.forName(className);
            Object obj = clazz.newInstance();
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

要更換具體工廠時,通過XMLUtil類的靜態方法getFactory()方法讀取配置檔案中的工廠類名進行物件的例項化,程式碼修改如下:

public class Client {

    public static void main(String[] args) {
        AbstractFactory factory;
        factory = (AbstractFactory) XMLUtil.getFactory();
        Shape shape = factory.getShape();
        shape.draw();
    }
}

過載抽象工廠類的工廠方法可以實現多種方式初始化具體產品類,例如提供無引數的預設實現,還可以提供包含一個字串引數的實現。

public abstract class AbstractFactory {

    public abstract Shape getShape();

    public abstract Shape getShape(String args);

    public abstract Shape getShape(Object obj);
}

具體的工廠類實現過載的工廠方法,這些方法可以包含不同的業務邏輯,以滿足對不同產品物件的需求。

3. 模式總結

工廠方法模式是簡單工廠模式的延伸,它繼承了簡單工廠模式的優點,同時還彌補了簡單工廠模式的不足。工廠方法模式是使用頻率最高的設計模式之一,是很多開源框架和API類庫的核心模式。

  1. 主要優點
    ① 在工廠方法模式中,工廠方法用來建立客戶所需要的產品,同時還向客戶隱藏了哪種具體產品類將被例項化這一細節,使用者只需要關心所需產品對應的工廠,無須關心建立細節。
    ② 基於工廠角色和產品角色的多型性設計是工廠方法模式的關鍵。它能夠讓工廠可以自主確定建立何種產品物件,而如何建立這個物件的細節則完全封裝在具體工廠內部。
    ③ 使用工廠方法模式的另一個優點是在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的介面,無須修改客戶端,也無須修改其他的具體工廠和具體產品,而只要新增一個具體工廠和具體產品即可,系統的可擴充套件性也就變得非常好,完全符合“開閉原則”。
  2. 主要缺點
    ① 在新增新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,系統中類的個數將成對增加,在一定程度上增加了系統的複雜度。
    ② 由於考慮到系統的可擴充套件性,需要引入抽象層,在客戶端程式碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度。
  3. 適用場景
    ① 客戶端不知道它所需要的物件的類。在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品物件由具體工廠類建立,可將具體工廠類的類名儲存在配置檔案中。
    ② 抽象工廠類通過其子類來指定建立哪個物件。在工廠方法模式中,對於抽象工廠類只需要提供一個建立產品的介面,而由其子類來確定具體要建立的物件,利用面向物件的多型性和里氏代換原則,在程式執行時,子類物件將覆蓋父類物件,從而使得系統更容易擴充套件。

4. 思考

有人說:可以在客戶端程式碼中直接通過反射機制來生成產品物件,在定義產品物件時使用抽象型別,同樣可以確保系統的靈活性和可擴充套件性,增加新的具體產品類無須修改原始碼,只需要將其作為抽象產品類的子類再修改配置檔案即可,根本不需要抽象工廠類和具體工廠
類。

試思考這種做法的可行性?如果可行,這種做法是否存在問題?為什麼?