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)