Fork me on GitHub

Programming Design Notes

Design Pattern - Factory

| Comments

Factory Pattern 可以將創造實例的程式碼分離,令原本核心的程式碼不必太過依賴於某一個物件, 並可以令其他要用到同一物件的地方使用同一套程式碼,方便集中管理。

看看以下例子應該有助你明白這個模式:


公司要你開發一個 Logger (記錄器),用於記錄程式內的變化,你一開始可能會設計成以下這樣:

(Logger.java) download
public interface Logger {

    public void debug(String message);
    public void info(String message);
    public void warn(String message);
    public void error(String message);

}
(ConsoleLogger.java) download
import java.util.Date;

public class ConsoleLogger implements Logger {

    private Class<?> clazz;

    public ConsoleLogger(Class<?> clazz){
        this.clazz = clazz;
    }

    @Override
    public void debug(String message) {
        log("DEBUG", message);
    }

    @Override
    public void info(String message) {
        log("INFO", message);
    }

    @Override
    public void warn(String message) {
        log("WARN", message);
    }

    @Override
    public void error(String message) {
        log("ERROR", message);
    }

    protected void log(String level, String message) {
        System.out.println(new Date().toString() + " " + clazz.getSimpleName() + " [" + level + "] " + message);
    }

}

其他程式要使用時會直接使用 new ConsoleLogger() 來取得 ConsoleLogger 的 Instance (實例)。

(AService.java) download
public class AService {

    private final Logger logger = new ConsoleLogger(this.getClass());

    public void doSomething(){
        logger.info("doSomething start");

        // code...

        logger.info("doSomething end");
    }

}
(BService.java) download
public class BService {

    private final Logger logger = new ConsoleLogger(this.getClass());

    public void doSomething(){
        logger.info("doSomething start");

        // code...

        logger.info("doSomething end");
    }

}

有沒有發現什麼問題? 如果使用 Logger 的物件超過 100 個,要轉換 ConsoleLogger 為其他 Logger 就要更改 100 次程式碼。


我們可以使用 Factory Pattern 來解決這個問題:

(Logger.java) download
public interface Logger {

    public void debug(String message);
    public void info(String message);
    public void warn(String message);
    public void error(String message);

}
(ConsoleLogger.java) download
import java.util.Date;

public class ConsoleLogger implements Logger {

    private Class<?> clazz;

    public ConsoleLogger(Class<?> clazz){
        this.clazz = clazz;
    }

    @Override
    public void debug(String message) {
        log("DEBUG", message);
    }

    @Override
    public void info(String message) {
        log("INFO", message);
    }

    @Override
    public void warn(String message) {
        log("WARN", message);
    }

    @Override
    public void error(String message) {
        log("ERROR", message);
    }

    protected void log(String level, String message) {
        System.out.println(new Date().toString() + " " + clazz.getSimpleName() + " [" + level + "] " + message);
    }

}
(FileLogger.java) download
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;


public class FileLogger implements Logger {

    private Class<?> clazz;

    public FileLogger(Class<?> clazz){
        this.clazz = clazz;
    }

    @Override
    public void debug(String message) {
        log("DEBUG", message);
    }

    @Override
    public void info(String message) {
        log("INFO", message);
    }

    @Override
    public void warn(String message) {
        log("WARN", message);
    }

    @Override
    public void error(String message) {
        log("ERROR", message);
    }

    protected void log(String level, String message) {
        String log = new Date().toString() + " " + clazz.getSimpleName() + " [" + level + "] " + message + "\n";

        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(new FileWriter("application.log", true));
            bw.append(log);
            bw.flush();
        } catch (Exception e) {
            System.out.println(e);
        } finally {
            try {
                bw.close();
            } catch (IOException e) {
                System.out.println(e);
            }
        }
    }

}
(LoggerFactory.java) download
public class LoggerFactory {

    public static Logger createLogger(Class<?> clazz){
        return new FileLogger(clazz);
    }

}

新增一個 LoggerFactory,用來建立 Logger 。

(AService.java) download
public class AService {

    private final Logger logger = LoggerFactory.createLogger(this.getClass());

    public void doSomething(){
        logger.info("doSomething start");

        // code...

        logger.info("doSomething end");
    }

}
(BService.java) download
public class BService {

    private final Logger logger = LoggerFactory.createLogger(this.getClass());

    public void doSomething(){
        logger.info("doSomething start");

        // code...

        logger.info("doSomething end");
    }

}

以後要使用 Logger 時不必知道實際是使用 ConsoleLogger 或 FileLogger,只要物件實現了 Logger 這個介面就可以了。 這個 Factory Pattern 解決了一些問題,但仍然差一點,例如: 在測試環境中我想使用 ConsoleLogger, 但在正常環境中是使用 FileLogger,但以上的方法並不能解決這個問題。


將 LoggerFactory 的 createLogger 變更為 Non-Static:

(LoggerFactory.java) download
public class LoggerFactory {

    public Logger createLogger(Class<?> clazz){
        return new FileLogger(clazz);
    }

}

新増一個 MockLoggerFactory 在測試環境:

(MockLoggerFactory.java) download
public class MockLoggerFactory extends LoggerFactory {

    @Override
    public Logger createLogger(Class<?> clazz) {
        return new ConsoleLogger(clazz);
    }


}
(AService.java) download
public class AService {

    private Logger logger;

    public AService(LoggerFactory loggerFactory){
        logger = loggerFactory.createLogger(this.getClass());
    }

    public void doSomething(){
        logger.info("doSomething start");

        // code...

        logger.info("doSomething end");
    }

}
(BService.java) download
public class BService {

    private Logger logger;

    public BService(LoggerFactory loggerFactory){
        logger = loggerFactory.createLogger(this.getClass());
    }

    public void doSomething(){
        logger.info("doSomething start");

        // code...

        logger.info("doSomething end");
    }

}
(Main.java) download
public class Main {

    public static void main(String[] args) {
        LoggerFactory loggerFactory = new LoggerFactory();
        AService a = new AService(loggerFactory);
        BService b = new BService(loggerFactory);
        a.doSomething();
        b.doSomething();

        //Unit Test
        loggerFactory = new MockLoggerFactory();
        a = new AService(loggerFactory);
        b = new BService(loggerFactory);
        a.doSomething();
        b.doSomething();
    }

}

這樣就可以在不同環境替換 LoggerFactory 來建立不同的 Logger。


以下的圖片是描述 Factory Pattern 的 Class Diagram。(From Wiki)