一. 程序設計目標
我們組寫了個簡單的水果生產程序,描述農場種植水果的過程,旨在通過此次設計更進一步了解工程設計模式,加強編程的結構化能力。
開發環境:JDK1.5
開發工具:JBuilder 2006
二.程序設計介紹
1.程序結構
我們組為一個水果公司寫了個簡單的生產程序,該公司專門向市場銷售各類水果。我們為程序建立了一個名為farm的工程,程序結構比較簡單,總共有7個類,并且都放在一個默認的包中。其層次結構可從下圖體現出來:
對各個類的說明:
Fruit類:水果接口,實現水果方法
Apple類:蘋果類,實現Fruit接口
Grape類:葡萄類,實現Fruit接口
Strawberry類:草莓類,實現Fruit接口
FruitGardener類:園丁類,可種植各種水果
BadFruitException類:要種植的水果不在公司經營的水果范圍之內,拋出種植異常
PlantFruit類:實現main()方法
2.程序設計步驟
在這個系統里需要描述下列的水果:
葡萄 Grape
草莓 Strawberry
蘋果 Apple
水果與其他的植物有很大的不同,就是水果最終是可以采摘食用的。那么一個自然的
作法就是建立一個各種水果都適用的接口,以便與農場里的其他植物區分開。如下圖所示。
水果接口規定出所有的水果必須實現的接口,包括任何水果類必須具備的方法:種植plant(),生長grow()以及收獲harvest()。接口Fruit 的類圖如下所示。
這個水果接口的源代碼如下所示。
代碼清單1:接口Fruit 的源代碼
public interface Fruit {
// 生長
void grow();
//收獲
void harvest();
//種植
void plant();
}描述蘋果的Apple 類的源代碼的類圖如下所示。
Apple 類是水果類的一種,因此它實現了水果接口所聲明的所有方法。另外,由于蘋果是多年生植物,因此多出一個treeAge 性質,描述蘋果樹的樹齡。下面是這個蘋果類的源代碼。
代碼清單2:類Apple 的源代碼
public class Apple
implements Fruit {
private int treeAge;
//生長
public void grow() {
log("Apple is growing...");
}
// 收獲
public void harvest() {
log("Apple has been harvested.");
}
//種植
public void plant() {
log("Apple has been planted.");
}
// 輔助方法
public static void log(String msg) {
System.out.println(msg);
}
//樹齡的取值方法
public int getTreeAge() {
return treeAge;
}
// 樹齡的賦值方法
public void setTreeAge(int treeAge) {
this.treeAge = treeAge;
}
}
同樣,Grape 類是水果類的一種,也實現了Fruit 接口所聲明的所有的方法。但由于葡萄分有籽和無籽兩種,因此,比通常的水果多出一個seedless 性質,如下圖所示。
葡萄類的源代碼如下所示。可以看出,Grape 類同樣實現了水果接口,從而是水果類型的一種子類型。
代碼清單3:類Grape 的源代碼
public class Grape
implements Fruit {
private boolean seedless;
//生長
public void grow() {
log("Grape is growing...");
}
//收獲
public void harvest() {
log("Grape has been harvested.");
}
//種植
public void plant() {
log("Grape has been planted.");
}
//輔助方法
public static void log(String msg) {
System.out.println(msg);
}
// 有無籽的取值方法
public boolean getSeedless() {
return seedless;
}
//有無籽的賦值方法
public void setSeedless(boolean seedless) {
this.seedless = seedless;
}
}
下圖所示是Strawberry 類的類圖。
Strawberry 類實現了Fruit 接口,因此,也是水果類型的子類型,其源代碼如下所示。
代碼清單4:類Strawberry 的源代碼
public class Strawberry
implements Fruit {
//生長
public void grow() {
log("Strawberry is growing...");
}
//收獲
public void harvest() {
log("Strawberry has been harvested.");
}
//種植
public void plant() {
log("Strawberry has been planted.");
}
//輔助方法
public static void log(String msg) {
System.out.println(msg);
}
}
農場的園丁也是系統的一部分,自然要由一個合適的類來代表。這個類就FruitGardener 類,其結構由下面的類圖描述。
FruitGardener 類會根據客戶端的要求,創建出不同的水果對象,比如蘋果(Apple),葡萄(Grape)或草莓(Strawberry)的實例。而如果接到不合法的要求,FruitGardener 類會拋出BadFruitException 異常,如下圖所示。
園丁類的源代碼如下所示。
代碼清單5:FruitGardener 類的源代碼
public class FruitGardener {
//靜態工廠方法
public static Fruit factory(String which) throws BadFruitException {
if (which.equalsIgnoreCase("apple")) {
return new Apple();
}
else if (which.equalsIgnoreCase("strawberry")) {
return new Strawberry();
}
else if (which.equalsIgnoreCase("grape")) {
return new Grape();
}
else {
throw new BadFruitException("Bad fruit request");
}
}
}
可以看出,園丁類提供了一個靜態工廠方法。在客戶端的調用下,這個方法創建客戶端所需要的水果對象。如果客戶端的請求是系統所不支持的,工廠方法就會拋出一個BadFruitException 異常。這個異常類的源代碼如下所示。
代碼清單6:BadFruitException 類的源代碼
public class BadFruitException
extends Exception {
public BadFruitException(String msg) {
super(msg);
}
}
在使用時,客戶端只需調用FruitGardener 的靜態方法factory()即可。請見下面的示意性客戶端源代碼。
代碼清單7:實現種植即Main()的實現
public class PlantFruit {
public PlantFruit() {
}
public static void main(String[] args) {
PlantFruit plantfruit = new PlantFruit();
try {
//種植葡萄
FruitGardener.factory("grape").plant();
FruitGardener.factory("grape").grow();
FruitGardener.factory("grape").harvest();
System.out.println("==================================");
//種植蘋果
FruitGardener.factory("apple").plant();
FruitGardener.factory("apple").grow();
FruitGardener.factory("apple").harvest();
System.out.println("==================================");
//種植草莓
FruitGardener.factory("strawberry").plant();
FruitGardener.factory("strawberry").grow();
FruitGardener.factory("strawberry").harvest();
System.out.println("==================================");
}
catch (BadFruitException e) {
}
}
}
到此為止,我們的簡單程序已經設計完成,我們可以通過創建FruitGardener對象來完成水果的種植,無論你要種什么,只需調用對象中的factory()方法。輸出結果如下:
三.簡單工廠模式的定義
簡單工廠模式是類的創建模式,又叫做靜態工廠方法(Static Factory Method)模式。簡單工廠模式是由一個工廠對象決定創建出那一種產品類的實例。
四.簡單工廠模式的結構
簡單工廠模式是類的創建模式,這個模式的一般性結構如下圖所示。
角色與結構
簡單工廠模式就是由一個工廠類可以根據傳入的參量決定創建出哪一種產品類的實例。下圖所示為以一個示意性的實現為例說明簡單工廠模式的結構。
從上圖可以看出,簡單工廠模式涉及到工廠角色、抽象產品角色以及具體產品角色等
三個角色:
(1)工廠類(Creator)角色:擔任這個角色的是工廠方法模式的核心,含有與應用緊
密相關的商業邏輯。工廠類在客戶端的直接調用下創建產品對象,它往往由一個
具體Java 類實現。
(2)抽象產品(Product)角色:擔任這個角色的類是工廠方法模式所創建的對象的父
類,或它們共同擁有的接口。抽象產品角色可以用一個Java 接口或者Java 抽象類
實現。
(3)具體產品(Concrete Product)角色:工廠方法模式所創建的任何對象都是這個角
色的實例,具體產品角色由一個具體Java 類實現。
工廠類的示意性源代碼如下所示。可以看出,這個工廠方法創建了一個新的具體產品的實例并返還給調用者。
代碼清單8:Creator 類的源代碼
public class Creator
{
//靜態工廠方法
public static Product factory()
{
return new ConcreteProduct();
}
}
抽象產品角色的主要目的是給所有的具體產品類提供一個共同的類型,在最簡單的情況下,可以簡化為一個標識接口。所謂標識接口,就是沒有聲明任何方法的空接口。
代碼清單9:抽象角色Product 接口的源代碼
public interface Product
{
}
具體產品類的示意性源代碼如下。
代碼清單10:具體產品角色ConcreteProduct 類的源代碼
public class ConcreteProduct implements Product
{
public ConcreteProduct(){}
}
雖然在這個簡單的示意性實現里面只給出了一個具體產品類,但是在實際應用中一般都會遇到多個具體產品類的情況。
五.簡單工廠模式的實現
1.多層次的產品結構
在真實的系統中,產品可以形成復雜的等級結構,比如下圖所示的樹狀結構上就有多個抽象產品類和具體產品類。
這個時候,簡單工廠模式采取的是以不變應萬變的策略,一律使用同一個工廠類。如下圖所示。
圖中從Factory 類到各個Product 類的虛線代表創建(依賴)關系;從Client 到其他類的聯線是一般依賴關系。這樣做的好處是設計簡單,產品類的等級結構不會反映到工廠類中來,從而產品類的等級結構的變化也就不會影響到工廠類。但是這樣做的缺點是,增加新的產品必將導致工廠類的修改。
2. 使用Java 接口或者Java 抽象類
如果模式所產生的具體產品類彼此之間沒有共同的商業邏輯,那么抽象產品角色可以由一個Java 接口扮演;相反,如果這些具體產品類彼此之間確有共同的商業邏輯,那么這些公有的邏輯就應當移到抽象角色里面,這就意味著抽象角色應當由一個抽象類扮演。在一個類型的等級結構里面,共同的代碼應當盡量向上移動,以達到共享的目的,如下圖所示。
六.模式的優點和缺點
1. 模式的優點
模式的核心是工廠類。這個類含有必要的判斷邏輯,可以決定在什么時候創建哪一個產品類的實例。而客戶端則可以免除直接創建產品對象的責任,而僅僅負責“消費”產品。簡單工廠模式通過這種做法實現了對責任的分割。
2. 模式的缺點
正如同在本章前面所討論的,當產品類有復雜的多層次等級結構時,工廠類只有它自
己。以不變應萬變,就是模式的缺點。這個工廠類集中了所有的產品創建邏輯,形成一個無所不知的全能類,有人把這種類叫做上帝類(God Class)。如果這個全能類代表的是農場的一個具體園丁的話,那么這個園丁就需要對所有的產品負責,成了農場的關鍵人物,他什么時候不能正常工作了,整個農場都要受到影響。將這么多的邏輯集中放在一個類里面的另外一個缺點是,當產品類有不同的接口種類時,工廠類需要判斷在什么時候創建某種產品。這種對時機的判斷和對哪一種具體產品的判斷邏輯混合在一起,使得系統在將來進行功能擴展時較為困難。這一缺點在工廠方法模式中得到克服。
由于簡單工廠模式使用靜態方法作為工廠方法,而靜態方法無法由子類繼承,因此,工廠角色無法形成基于繼承的等級結構。這一缺點會在工廠方法模式中得到克服。
七.個人體會
設計模式實際上是良好的OO思想的一種提煉。每一種設計模式后面都體現了一種良好的OO思路,這些思路對于解決軟件中常見的“change”問題有很大的適應性,而每種模式又有自己獨特的解決思路,帶有一定的通用性。而組合各種模式又可以解決許多常見問題。不可否認的是,還存在一些未經總結的設計模式。實際上,你自己也可以總結一些模式出來。無論怎樣,設計模式仍然是面向對象,它不是新東西,也沒有必要言必稱設計模式—似乎不懂設計模式就落伍了,但給OO的開發者提供一個言簡意賅的溝通橋梁。
設計模式告訴了我們什么是好的OO思想,思考如何更好的應用OO的思想—雖然還是那幾個耳熟能詳的術語:封裝、繼承、組合、多態。
設計模式首先是對傳統的OO使用方法的矯正:如針對接口編程而不是實現;優先使用組合,而不是繼承。其次是在原來理解上的突破:封裝是對變化而言的,不僅僅是屬性和方法的集合。類不僅是現實事物的抽象,同時它還具有責任。更有創新:依賴式注入。
模式不是萬能的,也并不總能完美地解決問題,因此每種模式都包括了影響的信息。在應用模式之前,我們必須先分析問題的情境,并評估模式的影響,再決定是否采用模式,采用哪一種模式。也就是說,理解、分析模式,和實現模式一樣重要
八.建議
(1)使用更加通俗易懂的語言解釋設計模式,并用完整的代碼實例輔以說明。代碼的演示時間應延長點,好讓學生看清,看懂代碼。
(2)在學生需要的時候給學生補充一點java知識,有些同學專注于其他語言,對于java也不太懂,聽起課來一頭霧水,這時候來點知識補充還是必要的。