從今天開始,我們就要正式開始學習設計模式了。關於設計模式的重要性,不言而喻,你寫的是代碼還是詩,一個重要的考察維度就是代碼的健壯性,可擴展性,這些都離不開設計模式的支持。
本文,我們就從最簡單的工廠方法模式開始,帶大家揭開設計模式的神祕面紗。
簡單工廠
要學習工廠方法,我們得先來學習下簡單工廠模式。工廠方法模式則是在簡單工廠的基礎上做的進一步優化。
模式定義
簡單工廠模式也叫靜態工廠方法模式,它是一種創建型模式。創建型模式還包括工廠方法模式、抽象工廠模式、建造者模式、單例模式以及原型模式。
在簡單工廠模式中,我們定義一個專門的類用來創建其他的實例,這個專門定義的類會根據不同的參數,返回不同的實例,這些不同的實例一般來說都有一個共同的父類。
但是需要注意的是,簡單工廠模式並不屬於 GOF 定義的 23 種設計模式。
模式抽象
我們先來通過一個簡單的 UML 圖來理解下簡單工廠模式到底是什麼樣子的:
圖-2
從這個 UML 圖中可以看到,在簡單工廠模式中,我們會首先定義一個產品的抽象類,然後每一個具體的產品都是實現這個抽象類,再通過工廠方法來創建一個產品的實例。
實例分析
我們通過一個生活中常見的場景來給大家描述一下這個模式。
例如我現在有兩臺電腦,一臺臺式機,另一臺是筆記本,兩臺電腦都有一個開機的方法,如下:
public class MacBook {
public void open() {
System.out.println("MacBook open...");
}
}
public class MacPro {
public void open() {
System.out.println("MacPro open...");
}
}
如果今天打算用 MacPro 來 coding 的話,那我就按照如下方式開機:
MacPro macPro = new MacPro();
macPro.open();
如果我今天打算用 MacBook 來 coding 的話,那我就按照如下方式開機:
MacBook macBook = new MacBook();
macBook.open();
上面的代碼看着沒問題,但是它畢竟是代碼,還不是詩,我們要將它變成詩。
首先,既然都是電腦,都有開機的方法,問什麼不能統一處理開機問題呢?
於是,我們首先來定義一個 Mac 接口,如下:
public interface Mac {
void open();
}
這個 Mac 就相當於我們上文說的抽象產品,然後讓 MacPro 和 MacBook 分別實現這個接口,並且實現接口中的 open 方法,它們是具體的產品,如下:
public class MacPro implements Mac{
public void open() {
System.out.println("MacPro open...");
}
}
public class MacBook implements Mac{
public void open() {
System.out.println("MacBook open...");
}
}
最終的類結構如下圖:
圖-3
最後我們在通過一個工廠類來提供具體產品的實例:
public class MacFactory {
public static Mac getInstance(String type) throws Exception {
if ("macpro".equals(type)) {
return new MacPro();
} else if ("macbook".equals(type)) {
return new MacBook();
}
throw new Exception("");
}
}
這樣,當用戶需要獲取 MacBook 或者 MacPro 的實例時,只需要通過給工廠方法傳入不同的參數,就可以獲取到這個實例。如下:
Mac mac = MacFactory.getInstance("macbook");
mac.open();
這就是我們一直心心心念唸的簡單工廠。那麼這麼做到底有什麼好處呢?
優缺點分析
優點
在簡單工廠模式中,有三個重要的元素,就是我們上面 UML 圖中的抽象產品、具體產品以及具體工廠,三個元素中,最最核心的當屬工廠類了,工廠類根據具體的需要來創建不同的實例,需求方只需要告訴工廠它需要創建什麼實例即可,剩下的事情就交給工廠類去完成,需求方不需要去創建產品的實例,實現一個解耦。
缺點
有優點,當然就會有缺點,最大的缺點在於這個工廠類不夠靈活,當產品增加的時候,我們就得修改這個工廠類,不夠友好;並且由於我們使用了靜態方法,導致簡單工廠也沒法形成繼承結構。
對於這些問題,我們在下面的設計模式中將會逐個解決。
實際應用
簡單工廠在 Java 中還是有很多非常廣泛的應用,例如格式化一個本地日期:
public final static DateFormat getDateInstance();
public final static DateFormat getDateInstance(int style);
public final static DateFormat getDateInstance(int style,Locale locale);
這就是簡單工廠模式,接下來我們將在工廠模式種對此做進一步的優化。
工廠方法
工廠方法模式是對簡單工廠的一個升級。我們來具體看下。
模式定義
工廠方法模式也屬於創建型模式,又名工廠模式、虛擬構造器模式或者多態工廠模式。在工廠方法模式中,工廠父類負責定義創建產品對象的公共接口,而工廠子類則負責生成具體的產品對象,這樣做的目的是將產品類的實例化操作延遲到工廠子類中完成,即通過工廠子類來確定究竟應該實例化哪一個具體產品類。
在簡單工廠模式中,實例的創建都是在工廠類中完成的,如果添加了新產品就得修改工廠方法,現在,我們對工廠類也進行抽象,抽出一個接口,然後創建多個工廠類,不同的工廠類創建不同的產品,這樣,如果添加新的產品線,我們只需要提供一個相應的工廠類即可。
模式抽象
我們通過一個簡單的 UML 圖來看下工廠模式是什麼樣子:
圖-4
從這張圖中可以看到,比上面多了一個抽象工廠而已。
實例分析
接下來,我們在上文的基礎上,繼續來完善。
首先 MacPro 和 MacBook 的定義就不需要變了,我們只需要重新定義 MacFactory,首先我們來定義 MacFactory:
public interface MacFactory {
Mac getMac();
}
然後再來定義 MacFactory 的實現類:
public class MacBookFactory implements MacFactory {
public Mac getMac() {
return new MacBook();
}
}
public class MacProFactory implements MacFactory {
public Mac getMac() {
return new MacPro();
}
}
在這裏我們分別定義了兩個實現類,不同的實現類用來創建不同的 Mac 對象。
最終,通過如下方式來創建實例:
MacFactory macProFactory = new MacProFactory();
Mac mac = macProFactory.getMac();
mac.open();
MacFactory macBookFactory = new MacBookFactory();
Mac mac2 = macBookFactory.getMac();
mac2.open();
這就是我們說的工廠方法模式,就比上面的多了一個工廠類的抽象,以後如果有新的產品上線,我們只需要提供相關工廠類即可。
優缺點分析
優點
- 工廠類滿足單一職責原則,一個工廠類只創建一個類。
- 符合開-閉原則。
- 實例方法,可以形成工廠類的等級結構。
缺點
- 每次添加新產品,都需要提供對應的工廠類。
- 一個具體的工廠只能創建一種產品。
- 如果要換 Mac 系的產品,還是要修改工廠類。
實際應用
工廠方法設計模式應用相當廣泛,最典型的莫過於 java.util.Collection
接口中的 iterator()
方法。
圖-5
在這張圖中,Collection 和 Iterator 分別相當於產品的抽象類和工廠的抽象類。