研究和使用創立性模式的必要性
面向對象的設計的目的之一,就是把責任進行劃分,以分派給不同的對象。我們推薦這種劃分責任的作法, 是因為它和封裝(Encapsulation)和分派(Delegation)的精神是相符合的。創立性模式把對象的創立過程封裝起來,使得創立實例的責任與使用實例的責任分割開, 並由專門的模塊分管實例的創立,而系統在宏觀上不再依賴於對象創立過程的細節。
所有面向對象的語言都有固定的創立對象的辦法。Java語的辦法就是使用new操作符。比如
StringBuffer s = new StringBuffer(1000);
就創立了一個對象s,其類型是StringBuffer。使用new操作符的短處是事先必須明確知道要實例化的類是什麼, 而且實例化的責任往往與使用實例的責任不加區分。使用創立性模式將類實例化,首先不必事先知道每次是要實例化哪一個類, 其次把實例化的責任與使用實例的責任分割開來,可以彌補直接使用new操作符的短處。
而工廠模式就是專門負責將大量有共同接口的類實例化,而且不必事先知道每次是要實例化哪一個類的模式。
工廠模式有幾種形態
工廠模式有以下幾種形態:
簡單工廠(Simple Factory)模式
工廠方法(Factory Method)模式,又稱多形性工廠(Polymorphic Factory)模式
抽象工廠(Abstract Factory)模式,又稱工具箱(Kit或Toolkit)模式
介紹簡單工廠模式
比如說,你有一個描述你的後花園的系統,在你的後花園裡有各種的花,但還沒有水果。你現在要往你的系統裡引進一些新的類,用來描述下列的水果:
葡萄 Grapes
草莓 Strawberry
萍果 Apple
花和水果最大的不同,就是水果最終是可以采摘食用的。那麼,很自然的作法就是建立一個各種水果都適用的接口,這樣一來這些水果類作為相似的數據類型就可以和你的系統的其余部分,如各種的花有所不同,易於區分。
圖1. Grape, Strawberry和Apple是擁有共同接口FruitIF的類。
package com.javapatterns.simplefactory;
public interface FruitIF {
void grow();
void harvest();
void plant();
String color = null;
String name = null;
}
代碼清單1. 接口FruitIF的源代碼。這個接口確定了水果類必備的方法:種植plant(),生長grow(), 以及收獲harvest()。
package com.javapatterns.simplefactory;
public class Apple implements FruitIF
{
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; }
private int treeAge;
}
代碼清單2. 類Apple的源代碼。萍果是多年生木本植物,因此具備樹齡treeAge性質。
package com.javapatterns.simplefactory;
public class Grape implements FruitIF
{
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 getSeedful()
{
return seedful;
}
public void setSeedful(boolean seedful)
{
this.seedful = seedful;
}
private boolean seedful;
}
代碼清單3. 類Grape的源代碼。葡萄分為有籽與無籽兩種,因此具有seedful性質。
package com.javapatterns.simplefactory;
public class Strawberry implements FruitIF
{
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);
}
}
代碼清單4. 類Strawberry的源代碼。
你作為小花果園的主人兼園丁,也是系統的一部分,自然要由一個合適的類來代表,這個類就是 FruitGardener類。這個類的結構請見下面的UML類圖。
圖2. FruitGardener類圖。
FruitGardener類會根據要求,創立出不同的水果類,比如萍果Apple,葡萄Grape或草莓Strawberry的實例。而如果接到不合法的要求,FruitGardener類會給出例外BadFruitException。
圖3. BadFruitException類圖。
package com.javapatterns.simplefactory;
public class FruitGardener
{
public FruitIF 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");
}
}
}
代碼清單5. FruitGardener類的源代碼。
package com.javapatterns.simplefactory;
public class BadFruitException extends Exception
{
public BadFruitException(String msg)
{
super(msg);
}
}
代碼清單6. BadFruitException類的源代碼。
在使用時,只須呼叫FruitGardener的factory()方法即可
try
{
FruitGardener gardener = new FruitGardener();
gardener.factory("grape");
gardener.factory("apple");
gardener.factory("strawberry");
...
}
catch(BadFruitException e)
{
...
}
就這樣你的小果園一定會有百果豐收啦!
簡單工廠模式的定義
總而言之,簡單工廠模式就是由一個工廠類根據參數來決定創立出那一種產品類的實例。下面的UML類圖就精確定義了簡單工廠模式的結構。
圖4. 簡單工廠模式定義的類圖。
public class Creator
{
public Product factory()
{
return new ConcreteProduct();
}
}
public interface Product
{
}
public class ConcreteProduct implements Product
{
public ConcreteProduct(){}
}
代碼清單7. 簡單工廠模式框架的源代碼。
簡單工廠模式實際上就是我們要在後面介紹的,工廠方法模式的一個簡化了的情形。在讀者熟悉了本節所介紹的簡單工廠模式後,就不難掌握工廠方法模式了。
問答題
在本節開始時不是說,工廠模式就是在不使用new操作符的情況下,將......類實例化的嗎, 可為什麼在具體實現時,仍然使用了new操作符呢?
在本節的小果園系統裡有三種水果類,可為什麼在圖3.(簡單工廠模式定義的類圖) 中產品(Product)類只有一種呢?
請使用簡單工廠模式設計一個創立不同幾何形狀,如圓形,方形和三角形實例的描圖員(Art Tracer)系統。每個幾何圖形都要有畫出draw()和擦去erase()兩個方法。當描圖員接到指令,要求創立不支持的幾何圖形時,要提出BadShapeException例外。
請簡單舉例說明描圖員系統怎樣使用。
在簡單工廠模式的定義(見圖4)中和花果園例子中,factory()方法都是屬於實例的, 而非靜態的或是類的方法。factory()方法可不可以是靜態的方法呢?
問答題答案
對整個系統而言,工廠模式把具體使用new操作符的細節包裝和隱藏起來。當然只要程序是用Java語言寫的, Java語言的特征在細節裡一定會出現的。
圖3.(簡單工廠模式定義的類圖),是精減後的框架性類圖,用於給出這一模式的准確而精練的定義。產品(Product)類到底會有幾種,則要對每個系統作具體分析。
這裡給出問題的完整答案。描圖員(Art Tracer)系統的UML如下
系統的源代碼如下
package com.javapatterns.simplefactory.exercise;
public class ArtTracer
{
public Shape factory(String which) throws BadShapeException
{
if (which.equalsIgnoreCase("circle"))
{
return new Circle();
}
else if (which.equalsIgnoreCase("square"))
{
return new Square();
}
else if (which.equalsIgnoreCase("triangle"))
{
return new Triangle();
}
else
{
throw new BadShapeException(which);
}
}
}
代碼清單8. ArtTracer類的源代碼。
package com.javapatterns.simplefactory.exercise;
public interface Shape
{
void draw();
void erase();
}
代碼清單9. Shape接口的源代碼。
package com.javapatterns.simplefactory.exercise;
public class Square implements Shape
{
public void draw()
{
System.out.println("Square.draw()");
}
public void erase()
{
System.out.println("Square.erase()");
}
}
代碼清單10. Square類的源代碼。
package com.javapatterns.simplefactory.exercise;
public class Circle implements Shape
{
public void draw()
{
System.out.println("Circle.draw()");
}
public void erase()
{
System.out.println("Circle.erase()");
}
}
代碼清單11. Circle類的源代碼。
package com.javapatterns.simplefactory.exercise;
public class Triangle implements Shape
{
public void draw()
{
System.out.println("Triangle.draw()");
}
public void erase()
{
System.out.println("Triangle.erase()");
}
}
代碼清單12. Triangle類的源代碼。
package com.javapatterns.simplefactory.exercise;
public class BadShapeException extends Exception
{
public BadShapeException(String msg)
{
super(msg);
}
}
代碼清單13. BadShapeException類的源代碼。
描圖員(Art Tracer)系統使用方法如下
try
{
ArtTracer art = new ArtTracer();
art.factory("circle");
art.factory("square");
art.factory("triangle");
art.factory("diamond");
}
catch(BadShapeException e)
{
...
}
注意對ArtTracer類提出菱形(diamond)請求時,會收到BadShapeException例外。
顯然factory()可以是靜態的或是類的方法。本文這樣介紹簡單工廠模式,是為了能方便與後面介紹的工廠方法模式作一比較。