網頁

2018/4/10

Java 設計模式 簡單工廠模式 Simple Factory Pattern

簡單工廠模式(Simple Factory Pattern)又稱靜態方法工廠模式(Static Methods Factory Pattern),屬於建構式模式(Creational Patterns),目的是用來建構物件。

工廠模式又分為簡單工廠模式(Simple Factory Pattern),工廠方法模式(Factory Method Pattern)及抽象工廠模式(Abstract Factory Pattern)。

所謂的工廠(Factory)是指用來建立物件或產生實例的類別;換句話說,一個專門用來建立物件的類別就叫做工廠類別(Factory Class)。

工廠模式的主要概念為封裝物件建構的邏輯,也就是不要讓客戶端直接以new的方式建立物件,而是把建立物件的邏輯抽取出來放在工廠類別。

工廠類別提供用來建立物件的靜態方法,該方法依傳入參數產生不同的物件。

例如下面Loader介面有二個實作類別XMLLoaderJSONLoader負責載入不同格式的檔案。

Loader

package com.abc.demo.loader;

public interface Loader {

    void load();
    
}

XMLLoader用來載入xml檔。

XMLLoader

package com.abc.demo.loader;

public class XMLLoader implements Loader {

    XMLLoader() {
        // 複雜的建構邏輯
    }

    @Override
    public void load() {
        System.out.println("Load from XML");
    }

}

JSONLoader用來載入json檔。

JSONLoader

package com.abc.demo.loader;

public class JSONLoader implements Loader {
    
    JSONLoader() {
        // 複雜的建構邏輯
    }

    @Override
    public void load() {
        System.out.println("Load from JSON");
    }

}

原本要載入檔案時是直接在程式中用new建立所需的Loader,例如:

package com.abc.demo;

import com.abc.demo.loader.Loader;
import com.abc.demo.loader.XMLLoader;

public class Main {

    public static void main(String[] args) {
        // 建構物件所需參數的複雜邏輯
        Loader loader = new XMLLoader(); // 直接用new建立物件,把所需參數傳入
        loader.load();

    }

}

然而可能碰到以下問題:

  • 若類別的名稱改了,例如XMLLoader改為DOMLoader,則程式中所有new XMLLoader()的地方都要改為new DOMLoader()(不過IDE如Eclipse的Refactor -> Rename可快速修改名稱)。
  • 原本建構式不需參數,但若將來要傳入參數,或是建構物件的邏輯改了,則程式中有所有呼叫建構式的地方都要修改。
  • 直接用new建構物件會把建構邏輯細節混雜在客戶端的業務邏輯中。客戶端不需知道建立物件的細節,因為只是要取得實例來用而已。
  • 同性質類別(例如繼承同介面,擁有相同的方法)建構物件的邏輯可集中在同一處,不應該讓客戶端隨意建構物件。
  • Java的建構式名稱必須與類別名稱相同,但有時建構式的名稱無法充分反映建構式的行為。

而Simple Factory Pattern則是用來解決以上問題。我們將建構Loader具體類new XMLLoader()實例的動作封裝在一個工廠類別中,當需要實例時不直接用new,而是則透過工廠類別的靜態方法來建構物件,客戶端不需知道建構時例的細節。

JDK慣例用來建構有共同介面的類別實例的工廠類是不可實例化(noninstantiable)類別,且工廠類的名稱為介面的名稱加s。典型的例子為java.util.Collections,其提供各種建構繼承Collection介面的類別如ListSet的實例。因此Loader工廠類別命名為Loaders,靜態方法getLoader()依傳入參數決定要建構的實例。

Loaders (Loader Factory)

package com.abc.demo.loader;

public class Loaders {

    private Loaders() { // private建構式無法被實例化
        throw new AssertionError("Factory class cannot be instantiated")
    }

    public static Loader getLoader(LoaderType loaderType) {
        switch (loaderType) {
            case XML: {
                // 建構物件所需參數的複雜邏輯
                return new XMLLoader(...);
            }
            case JSON: {
                // 建構物件所需參數的複雜邏輯
                return new JSONLoader(...);
            }
            default: {
                return null;
            }
        }
    }

    public enum LoaderType {
        XML, JSON
    }

}

在客戶端建立Loader的方式改為透過Loaders.getLoader()產生。

package com.abc.demo;

import com.abc.demo.loader.Loader;
import com.abc.demo.loader.factory.LoaderFactory;

public class Main {

    public static void main(String[] args) {

        Loader loader = Loaders.getLoader(LoaderFactory.LoaderType.JSON);
        loader.load();

    }

}

透過Loaders工廠建立物件,則未來如果需要修改類別名稱、修改建構式參數、更換類別或邏輯等就只要修改getLoader()中的程式就好。

但未來如果要擴充其他的Loader,例如YAMLLoader,則必須修改LoaderFactory.getLoader()switch case,這樣違反了SOLID的開放封閉原則(Open/closed principle),添加新功能時不應該修改原有的程式碼。所以進一步把工廠建構不同類別的程序抽取出來成為工廠方法模式(Factory Method Pattern)




沒有留言:

張貼留言