本文講述一個披薩的誕生
我有一家披薩店,顧客來點了想吃的品種,然後我要準備材料、烘烤、剪下、幫顧客打包。這個過程用程式碼怎麼實現呢?
傳統方式
首先定義好準備、烘烤、剪下和打包這些動作
public abstract class Pizza {
protected String name;
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;
}
}
複製程式碼
然後定義一下都可以做哪些披薩種類
public class CheesePizza extend Pizza{
@Override
public void prepare(){
super.setname("CheesePizza");
System.out.println("CheesePizza");
}
}
複製程式碼
public class GreekPizza extends Pizza {
@Override
public void prepare() {
super.setname("GreekPizza");
System.out.println("GreekPizza");
}
}
複製程式碼
準備工作做完,就等著顧客來點餐了
public class OrderPizza {
public OrderPizza() {
String type = null;
Pizza pizza = null;
do {
type = getType();
if ("greek".equals(type)){
pizza = new GreekPizza();
}else if ("cheese".equals(type)){
pizza = new CheesePizza();
}else {
break;
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}while (true);
}
private String getType(){
String type = null;
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type: ");
type = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return type;
}
}
複製程式碼
整個過程如下:
顧客根據披薩的種類進行點餐,然後根據型別進行建立披薩物件,進行準備、烘焙、剪下、打包
很明顯,這樣設計是有問題的。如果店裡有了新品,或者下架某種類披薩,就要修改OrderPizza類中根據顧客輸入來建立物件這段程式碼,顯然是違背上文提到的開放封閉原則。如下程式碼,現在我們已經知道哪些會改變,哪些不會改變,是時候使用封裝了。
//需要修改
if ("greek".equals(type)){
pizza = new GreekPizza();
else if ("cheese".equals(type)){
pizza = new CheesePizza();
}else {
break;
}
//不需要修改
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
複製程式碼
簡單披薩工廠
現在最好將建立物件移到orderPizza()之外,但怎麼做呢?我們可以把建立披薩的程式碼移到另一個物件中,由這個新物件專職建立披薩。
我們稱這個新物件為“工廠”。
工廠(factory)處理建立物件的細節。一旦有了SimplePizzaFactory,orderPizza()就變成此物件的客戶。當需要披薩時,就叫披薩工廠做一個。那些orderPizza()方法需要知道希臘披薩或者蛤蜊披薩的日子一去不復返了。現在orderPizza()方法只關心從工廠得到了一個披薩,而這個披薩實現了Pizza介面,所以它可以呼叫prepare()、bake()、cut()、box()來分別進行準備、烘烤、切片、裝盒。
SimplePizzaFactory是我們的新類,它負責為客戶建立披薩
public class SimpleFactoryPizza {
public Pizza createPizza(String type){
Pizza pizza = null;
if ("greek".equals(type)) {
pizza=new GreekPizza();
}else if ("cheese".equals(type)) {
pizza=new CheesePizza();
}else if ("beef".equals(type)) {
pizza=new BeefPizza();
}
return pizza;
}
}
複製程式碼
新的order只需構造時傳入一個工廠,然後帶入訂單型別來使用工廠建立披薩,代替之前具體的例項化
public class OrderPizza {
SimpleFactoryPizza factory;
public OrderPizza(SimpleFactoryPizza factory) {
this.factory = factory;
}
Pizza orderPizza(String type) {
Pizza pizza = null;
pizza = factory.createPizza(type);
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}
return pizza;
}
}
複製程式碼
雖然看似程式碼同傳統方式一樣,但是我們已經將變化的程式碼抽取出來了,在OrderPizza中我們無需再次修改,此時我們已經將變化的和不變化的隔離開來了。
工廠模式
如果披薩店生意越來越好,考慮開幾家加盟店。身為加盟公司的經營者,你希望確保加盟店的運營質量,還希望各地的披薩有自己不同的區域特點。在推廣SimpleFactoryPizza時,發現加盟店採用的統一的工廠建立的披薩,但是其他部分卻開始採用自創的流程,比如烘烤方式,包裝方式等等。那麼如果建立一個框架,約束關鍵步驟的同時又能保持一定的彈性呢?
修改給披薩店使用的框架
有一個辦法可以讓披薩製作活動侷限於OrderPizza類,同時讓不同的店還擁有自己的特色。
所要做的事情就是把createPizza()方法放回到OrderPizza中,不過得將它設定成抽象方法,然後為每個店鋪建立一個OrderPizza的子類。
來看下修改後的OrderPizza:
public abstract class OrderPizza {
public Pizza orderPizza(String type){
Pizza pizza;
//將建立方法從工廠物件中移回OrderPizza中
pizza = createPizza(type);
//沒變過
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
//建立工廠方法改為抽象的,方便子類修改
abstract Pizza createPizza(String type);
}
複製程式碼
現在已經有一個OrderPizza作為父類,讓不同區域的店鋪來繼承OrderPizza,每個子類各自決定製作什麼風味的披薩。
允許子類(不同店鋪)做決定
OrderPizza已經有一個不錯的訂單系統,由orderPizza()負責處理訂單,而你希望所有加盟店對於訂單的處理都能一致。
各個區域披薩店之間的差異在於他們製作披薩的風味(紐約披薩的薄脆、芝加哥披薩的餅厚等),我們現在要讓現在createPizza()能夠應對這些變化來負責建立正確種類的披薩。做法是讓OrderPizza的各個子類負責定義自己的createPizza()方法。所以我們會得到一些OrderPizza具體的子類,每個子類都有自己的披薩變體,而仍然適合OrderPizza框架,並使用除錯好的orderPizza()方法。
// 如果加盟店為顧客提供紐約風味的披薩,就使用NyStyleOrderPizza,
// 因為此類的createPizza()方法會建立紐約風味的披薩
public class NyStyleOrderPizza extends OrderPizza{
//子類自己定義建立披薩方法
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new NyStyleCheesePizza();
} else if (type.equals("veggie")) {
pizza = new NyStyleVeggiePizza();
}
return pizza;
}
}
// 類似的,利用芝加哥子類,我們得到了帶芝加哥原料的createPizza()實現
public class ChicagoStyleOrderPizza extends OrderPizza{
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new ChicagoCheesePizza();
} else if (type.equals("veggie")) {
pizza = new ChicagoVeggiePizza();
}
return pizza;
}
}
複製程式碼
現在問題來了,OrderPizza的子類終究只是子類,如何能夠做決定?在NyStyleOrderPizza類中,並沒有看到任何做決定邏輯的程式碼。
關於這個方面,要從OrderPizza類的orderPizza()方法觀點來看,此方法在抽象的OrderPizza內定義,但是隻在子類中實現具體型別。
orderPizza()方法對物件做了許多事情(例如:準備、烘烤、切片、裝盒),但由於Pizza物件是抽象的,orderPizza()並不知道哪些實際的具體類參與進來了。換句話說,這就是解耦(decouple)!
當orderPizza()呼叫createPizza()時,某個披薩店子類將負責建立披薩。做哪一種披薩呢?當然是由具體的披薩店決定。
那麼,子類是實時做出這樣的決定嗎?不是,但從orderPizza()的角度看,如果選擇在NyStyleOrderPizza訂購披薩,就是由這個子類(NyStyleOrderPizza)決定。嚴格來說,並非由這個子類實際做“決定”,而是由“顧客”決定哪一家風味的披薩店才決定了披薩的風味。
我們來看下如何呼叫:
public class test {
public static void main(String[] args) {
OrderPizza orderPizza = new NyStyleOrderPizza();
Pizza pi = orderPizza.orderPizza("cheese");
System.out.println("-------");
Pizza pizza = orderPizza.orderPizza("veggie");
}
}
複製程式碼
執行結果如下:
Preparing NyStyleCheesePizza baking;
Preparing NyStyleCheesePizza cutting;
Preparing NyStyleCheesePizza boxing;
-------
Preparing NyStyleVeggiePizza baking;
Preparing NyStyleVeggiePizza cutting;
Preparing NyStyleVeggiePizza boxing;
複製程式碼
本文來源:《head-first設計模式》