設計模式:工廠設計模式介紹及3種寫法(簡單工廠、工廠方法、抽象工廠)
0、介紹
給一個背景:一個pizza訂購專案,pizza本身的種類要便於擴充套件和維護,那麼種類很多、製作過程也不少、還要完成訂購的功能。
按照一般的思路,類圖設計如下:
其中:
- Pizza類按照面向物件的設計思路,製作過程對應的方法在其中實現;
- 由於需要不同的型別 Pizza,而在 prepare 步驟不同,所以 Pizza 做成抽象類,而兩個不同型別的 Pizza 去繼承 Pizza 類;
- 還需要一個的 OrderPizza 類,接受預定型別,完成對應的pizza類訂購操作;
- 然後提供一個入口 PizzaStore 去呼叫 OrderPizza 類,相當於客戶端。
程式碼如下:
/* * Pizza抽象類,讓其他的繼承 */ public abstract class Pizza { protected String name;//pizza名 //不同pizza準備不同,所以留給實現類去實現 public abstract void prepare(); public void bake(){ System.out.println(name + " baking"); } public void cut(){ System.out.println(name + " cutting"); } public void box(){ System.out.println(name + " boxing"); } public void setName(String name){ this.name = name; } }
/*
* 乳酪pizza
*/
public class CheesePizza extends Pizza {
@Override
public void prepare() {
System.out.println("製作乳酪pizza準備材料ing");
}
}
/* * 希臘pizza */ public class GreekPizza extends Pizza { @Override public void prepare() { System.out.println("給希臘pizza準備原材料ing"); } }
/* * pizza訂購類 */ public class OrderPizza { //獲取pizza訂單 private String getType(){ try { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("input pizza type"); String type = br.readLine(); return type; }catch (IOException e){ e.printStackTrace(); return ""; } } //構造器 public OrderPizza() { Pizza pizza = null; String orderType;//訂購型別 do { orderType = getType(); if (orderType.equals("greek")){ pizza = new GreekPizza(); pizza.setName("希臘pizza"); }else if (orderType.equals("cheese")){ pizza = new CheesePizza(); pizza.setName("乳酪pizza"); }else{ break; } //方便除錯,把製作過程列印 pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); }while (true); } }
/*
* 客戶端
*/
public class PizzaStore {
public static void main(String[] args) {
new OrderPizza();
}
}
這樣我們的一個 pizza 線上商店就全部完成了,似乎是一個很完美的過程呢。
優點:
傳統思路,便於理解
缺點:
違反了在設計模式七大原則裡面說過的 ocp 原則,即對擴充套件開放,對修改關閉。
具體一些:Pizza 作為提供方,如果商店要增加一個種類,那麼除了要增加一個 Pizza 類【提供方】之外,依賴的 Order 類也要增加對於新 Pizza 的處理【使用方】(甚至擴充套件起來,還有別的依賴於 Pizza 的使用方操作類,全部都要修改),而按照 Ocp 原則,使用方不應該修改,所以我們要避免這種情況。
一、簡單工廠模式
對於上面的缺點,我們把建立 Pizza 的過程封裝到一個類裡,然後以這個類為中介進行建立,而不是再在Order裡面直接建立。這樣增加【提供方】 Pizza 的時候,修改這個類就可以了。
簡單工廠模式(靜態工廠模式):
- 屬於建立型模式,是工廠模式的一種,由一個工廠物件來決定創造出哪一種產品類的例項。他是工廠模式家族裡最簡單實用的模式;
- 簡單工廠模式定義了一個建立物件的類,由這個類來封裝例項化物件的行為;
- 在實際開發中,如果會用到大量建立某種物件時,就會用到工廠模式。
/*
簡單工廠類,根據型別建立pizza物件並返回
*/
public class SimpleFactory {
public Pizza createPizza(String orderType){
System.out.println("使用簡單工廠模式");
Pizza pizza = null;
if (orderType.equals("greek")){
pizza = new GreekPizza();
pizza.setName("希臘披薩");
}else if (orderType.equals("cheese")){
pizza = new CheesePizza();
pizza.setName("乳酪披薩");
}else if (orderType.equals("pepper")){
pizza = new PepperPizza();
pizza.setName("胡椒披薩");
}
return pizza;
}
}
/*
* pizza訂購類
*/
public class OrderPizza {
SimpleFactory factory;
Pizza pizza = null;
//構造器
public OrderPizza(SimpleFactory factory) {
setFactory(factory);
}
//建立過程改為呼叫工廠類
public void setFactory(SimpleFactory factory){
String orderType = "";
this.factory = factory;
do {
orderType = getType();
pizza = this.factory.createPizza(orderType);
//打印製作過程
if (pizza != null){
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}else{
System.out.println("訂購失敗");
break;
}
}while (true);
}
//獲取pizza種類
private String getType() {
//和前面一樣
}
}
/*
* 客戶端
*/
public class PizzaStore {
public static void main(String[] args) {
// new OrderPizza();
//使用簡單那工廠模式
new OrderPizza(new SimpleFactory());
System.out.println("結束");
}
}
類圖變成了這樣:
可以看到,複雜的依賴關係基本消失,工廠類 負責了所有物件的產生,因此他和使用方OrderPizza 的關係是聚合關係。
這樣一來,如果有新增的 Pizza 類,除了類的建立之外,就只用修改工廠類裡面的新建方法,而對於使用方,OrderPizza、或者即將使用 Pizza 類的其他增加進來的使用方,程式碼都不用修改,因為他們都是呼叫工廠類進行物件的建立。
(其實就是為了方便擴充套件,多抽象出來一層的思想)
補充:
簡單工廠模式之所以又叫靜態工廠模式,就是因為可以直接把工廠類的 create 方法寫成靜態,這樣的話,就不用 new 出工廠物件,而是隻需要呼叫它的靜態方法就可以。
public OrderPizzaviaStatic() {
String orderType = "";
do {
orderType = getType();
pizza = SimpleFactory.createPizza(orderType);//靜態方法
if (pizza != null){
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}else{
System.out.println("訂購失敗");
break;
}
}while (true);
}
使用靜態與否,根據具體情況來修改。
二、工廠方法模式
工廠方法模式:定義了一個建立物件的抽象方法,由子類決定要例項化的類,工廠方法模式將物件的例項化推遲到子類。
思考這個問題:
如果新增了一個需求,對於pizza,每次要新加地域屬性,比如現在還是3種pizza,但是有3個地方,各有這三種pizza。客戶點單的時候也會輸入地域屬性。
- 解決方法:建立的時候利用簡單工廠模式,新建不同地域的工廠,並且每個工廠裡帶上地域屬性,其他不變,就可以實現擴充套件。
- 但是這種方法不是最優,如果不同工廠很多的話,會需要多個工廠類。
其實把工廠類單獨作為普通類來考慮,我們能想到的改進思路就是,在他們基礎上,再提取公共介面或者抽象類。事實上,工廠方法模式的確是基於這種思路的。
使用工廠方法模式,將例項化功能抽象成抽象方法(而不是像簡單工廠模式那樣直接實現),在不同地域點餐子類中實現抽象方法。
根據這個類圖很容易看得出來,其實只是對於Order部分做了一個抽象,其他的和之前的一般寫法沒有區別。
雖然沒有出現factory字樣,但是其實兩個Order子類充當了Factory的作用:
- Order 根據不同的 type 去呼叫不同的子 Order,做 create 的工作;
- 每個 Pizza 類裡什麼也不做。
所以和上面簡單工廠的區別就是,這個根據地域分別寫了 Order 類,將 OrderPizza 作為抽象類去組織這幾個。
因此這一版程式碼,一個抽象 Pizza 類和4個子 Pizza 類完全沒有變化,剩下三個類的寫法如下:
/*
orderPizza類,根據type呼叫不同的工廠
*/
public abstract class OrderPizza {
//抽象方法,具體根據地域不同,讓工廠子類去實現
abstract Pizza createPizza(String orderType);
//構造器
public OrderPizza(){
Pizza pizza = null;
String orderType;
do {
orderType = getType();
pizza = createPizza(orderType);//呼叫的是抽象方法
if (pizza != null){
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}else {
System.out.println("訂購失敗");
break;
}
}while (true);
}
private String getType() {
//和之前一樣
}
}
/*
北京pizza工廠類
*/
public class BJOrderPizza extends OrderPizza{
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("cheese")){
pizza = new BJCheesePizza();
pizza.setName("北京乳酪pizza");
}else if (orderType.equals("pepper")){
pizza = new BJPepperPizza();
pizza.setName("北京胡椒pizza");
}
return pizza;
}
}
/*
上海pizza工廠類
*/
public class SHOrderPizza extends OrderPizza {
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("cheese")){
pizza = new SHCheesePizza();
pizza.setName("上海乳酪pizza");
}else if (orderType.equals("pepper")){
pizza = new SHPepperPizza();
pizza.setName("上海胡椒pizza");
}
return pizza;
}
}
最後,我們還是用一個 PizzaStore 類模擬一下客戶端,接受不同的輸入,來呼叫不同的 Order 類就可以。
三、抽象工廠模式
抽象工廠模式:
- 定義一個 interface 用於建立相關或有依賴關係的物件簇,而無需指明具體的類;
- 將簡單工廠模式和工廠方法模式進行整合,其實相當於將簡單工廠模式再網上抽象一層;
- 包含AbstractFactory(抽象工廠)和具體實現工廠子類,根據需要的型別使用對應的子類,這樣簡單工廠類變成了工廠簇,更有助於程式碼維護。
這種實現方式的類圖是這樣的:
相比上面的工廠方法模式,抽象模式抽象出來一個Factory介面,兩個實現類分別作為具體的工廠去實現介面的方法,然後聚合到Order類上,整體思路和上一種是一樣的。
程式碼層面:
所有Pizza的內容都一樣,BJFactory 和 SHFactory 和上面所說的,用工廠方法模式寫的程式碼也一樣,不過改成實現介面 AbstractFactory,而不是實現order抽象類。
AbstractFactory:
/*
抽象工廠模式的抽象層:介面
*/
public interface AbstractFactory {
//讓下面子類實現具體
public Pizza createPizza(String orderType);
}
OrderPizza 類和第二種模式的 OrderPizza 類基本是一樣的;
區別就在於,不是根據傳入的 Pizza 的 type 直接 create,而是去呼叫 Factory 實現類,所以是和第一種模式程式碼一樣。
/*
聚合的order類,和工廠介面互動
*/
public class OrderPizza {
AbstractFactory factory;
//構造器
public OrderPizza(AbstractFactory factory){
setFactory(factory);
}
//根據不同實現類進行構造
private void setFactory(AbstractFactory factory){
Pizza pizza = null;
String orderType = "";
this.factory = factory;
do {
orderType = getType();
//factory是接受的不同factory,取決於不同實現類
pizza = factory.createPizza(orderType);
if (pizza != null){
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}else{
System.out.println("訂購失敗");
break;
}
}while (true);
}
private String getType() {
}
}
最後,我們寫上 Store類就可以,根據輸入的不同,new出 Order 類,並且傳入不同地域的工廠引數。
總結一下可以發現,這三種工廠模式,大同小異,就是對於簡單工廠模式的不同程度的抽象。
四、工廠模式在JDK的應用
jdk 的 Canlendar類中,使用的就是 簡單工廠模式
當我們寫下這行程式碼:
Calendar calendar = Calendar.getInstance();//靜態方法
的時候,getInstance 方法去呼叫了
createCalendar 方法,返回一個 Canlendar 例項。
而在建立的方法裡面如下:
做一系列判斷,看裡面獲取的各種引數,相當於這個工廠根據不同的情況去決定選擇哪一種(具體在這個類裡是時區、地理位置之類的),最後將建立好的 calendar 返回。
也就是對應我們說的工廠模式第一種,簡單工廠模式。
五、總結
工廠模式的意義:
將例項化物件的程式碼提取出來,放到一個類裡面統一管理和維護,達到各種依賴關係的解耦,從而提高專案的可擴充套件性和可維護性。(最後是達到設計模式的 ocp 原則)
工廠模式分為三種:
- 簡單工廠模式:如果要建立一種物件的行為比較多,將所有的工作放到一個工廠類裡,每次呼叫這個類的方法就可以。
- 工廠方法模式:如果建立的物件種類要增加,那就分類多寫幾個工廠類,讓他們實現一個抽象方法,這樣能夠做了區分後呼叫不同的工廠類。
- 抽象工廠模式:再加一層抽象工廠層,那麼操作層面就只和固定的抽象工廠類互動,不用管有幾種工廠實現類。
工廠模式體現的編碼技巧:
- 建立物件例項的時候不要直接 new 類,而是要把這個動作交給一個工廠的方法裡,並且返回。
- 不要讓類直接繼承具體的類,而是繼承抽象類或者實現介面。
- 不要覆蓋基類中已經實現的方法。