工廠方法
工廠模式是一種設計模式,用於促進資料表示的封裝。
- 問題
- 我們希望在執行時根據一些配置或應用程式引數來決定要建立哪個物件。當我們編寫程式碼時,我們不知道應該例項化哪個類。
- 解決方案
- 定義一個用於建立物件的介面,但讓子類決定要例項化哪個類。工廠方法允許類將例項化推遲到子類。它的主要目的是提供一種方法讓使用者檢索具有已知編譯時型別的例項,但其執行時型別實際上可能不同。換句話說,一個應該返回類Foo例項的工廠方法可能會返回類Foo的例項,或者它可能會返回類Bar的例項,只要Bar繼承自Foo。這樣做的原因是它加強了實現者和客戶端之間的邊界,隱藏了資料的真實表示(參見抽象屏障)對使用者,從而允許實現者隨時更改此表示而不會影響客戶端,只要客戶端面向的介面沒有改變。
工廠模式的基本實現
實現工廠模式的一般模板是提供一個主要的使用者面向類,其中包含使用者可以用來獲取具有該型別的例項的靜態方法。然後建構函式對使用者來說是私有/受保護的,迫使他們使用靜態工廠方法來獲取物件。以下Java程式碼展示了工廠模式針對型別Foo的非常簡單的實現。
public class Foo {
// Static factory method
public static Foo getInstance() {
// Inside this class, we have access to private methods
return new Foo();
}
// Guarded constructor, only accessible from within this class, since it's marked private
private Foo() {
// Typical initialization code goes here
}
} // End class Foo
使用此程式碼,客戶端程式碼不可能使用new運算子來獲取類的例項,就像傳統上那樣
// Client code
Foo f = new Foo(); // Won't Work!
因為建構函式被標記為private。相反,客戶端必須使用工廠方法
// Client code
Foo f = Foo.getInstance(); // Works!
應該注意,即使在程式語言社群內部,關於工廠方法的命名約定也沒有普遍共識。有些人建議使用與普通建構函式類似的類名來命名方法,但以小寫字母開頭。其他人說這很混亂,建議使用訪問器型別語法,比如上面使用的getInstance樣式,儘管其他人抱怨這可能會錯誤地暗示單例實現。同樣,有些人提供newInstance,但這被批評為在某些情況下具有誤導性,在這些情況下,可能不會實際返回嚴格的new例項(再次參考單例模式)。因此,我們不會嘗試在這裡遵循任何特別嚴格的標準,我們將只嘗試使用最適合我們當前目的的名稱。
字母的工廠模式實現
這很好,您知道如何實現一個非常簡單的工廠模式,但這對您有什麼好處呢?使用者要求符合型別Foo的東西,他們得到的是類Foo的例項,這與直接呼叫建構函式有什麼區別?好吧,沒有區別,除了您在堆疊上添加了另一個函式呼叫(這是一件壞事)。但這只是針對上述情況。我們現在將討論工廠模式的更實用用途。考慮一個稱為Letter的簡單型別,它表示字母表中的一個字母,具有以下客戶端面向介面(即公共例項方法)
char toCharacter();
boolean isVowel();
boolean isConsonant();
我們可以很容易地在不使用工廠方法的情況下實現這一點,這可能類似於以下程式碼
public class Letter {
private char fTheLetter;
public Letter(char aTheLetter) {
fTheLetter = aTheLetter;
}
public char toCharacter() {
return fTheLetter;
}
public boolean isVowel() {
//TODO: we haven't implemented this yet
return true;
}
public boolean isConsonant() {
// TODO: we haven't implemented this yet
return false;
}
} // End class Letter
相當簡單,但請注意,我們還沒有實現最後兩個方法。我們仍然可以很容易地做到這一點。第一個可能看起來像這樣
public boolean isVowel() {
return
fTheLetter == 'a' ||
fTheLetter == 'e' ||
fTheLetter == 'i' ||
fTheLetter == 'o' ||
fTheLetter == 'u' ||
fTheLetter == 'A' ||
fTheLetter == 'E' ||
fTheLetter == 'I' ||
fTheLetter == 'O' ||
fTheLetter == 'U';
}
現在還不算太糟糕,但我們仍然需要做isConsonant。幸運的是,我們至少知道在這種情況下,如果它是母音,它就不是子音,反之亦然,所以我們最後一個方法可以簡單地是
public boolean isConsonant() {
return !this.isVowel();
}
那麼這裡的問題是什麼?基本上,每次呼叫這些方法中的任何一個時,您的程式都必須進行所有這些檢查。當然,這對 Java 執行時環境來說不是真正的負擔,但您可以想象一個更復雜、更耗時的操作。如果我們能夠避免每次呼叫方法時都這樣做,那不是很好嗎?例如,假設我們可以在建立物件時只做一次,然後就不必再做了。當然,我們可以做到。這是一個實現,它將為我們做到這一點,我們仍然不必使用工廠方法
public class Letter {
private char fTheLetter;
private boolean fIsVowel;
public Letter(char aTheLetter) {
fTheLetter = aTheLetter;
fIsVowel = fTheLetter == 'a' ||
fTheLetter == 'e' ||
fTheLetter == 'i' ||
fTheLetter == 'o' ||
fTheLetter == 'u' ||
fTheLetter == 'A' ||
fTheLetter == 'E' ||
fTheLetter == 'I' ||
fTheLetter == 'O' ||
fTheLetter == 'U';
}
public char toCharacter() {
return fTheLetter;
}
public boolean isVowel() {
return fIsVowel;
}
public boolean isConsonant() {
return !fIsVowel;
}
} // End class Letter
請注意,我們如何將冗長的操作移到建構函式中,並存儲結果。好的,現在我們一切都好,對嗎?當然,但假設您想出了一個新想法,一個不同的實現:您想將此型別拆分為兩個類,一個類處理母音,一個類處理子音。太好了,它們都可以是Letter類的子類,使用者永遠不會知道區別,對嗎?錯。客戶端如何才能訪問這些新類?他們有程式碼,透過呼叫new Letter('a')和new Letter('Z'),這些程式碼對他們來說執行得很好。現在您要讓他們遍歷所有程式碼並將這些程式碼更改為new Vowel('a')和new Consonant('Z')嗎?他們可能不會太高興。如果您可以從一個方法中獲取這兩個類的新的例項,那該多好!當然可以,只要在Letter類中使用一個靜態方法,它將執行與建構函式相同的單次檢查,並返回正確類的適當例項。您知道嗎,這是一個工廠方法!但這對您的客戶來說仍然沒有多大用處,他們仍然需要遍歷並將所有new Letter()更改為Letter.getLetter()。好吧,很遺憾,除非您放棄新的實現,否則您已經無能為力了。但這說明了從一開始就使用工廠方法的原因。面向物件程式設計的重要組成部分之一是您永遠不知道您的程式碼將來會在哪裡使用。透過充分利用抽象屏障並使用封裝友好的程式設計模式(如工廠模式),您可以更好地為自身和您的客戶做好應對未來更改具體實現的準備。特別是,它允許您使用“大錘”式方法以一種可能不太理想但快速的方式完成某事,以便滿足截止日期或推進測試。然後,您可以稍後返回並改進實現(資料表示和演算法),使其更快、更小或其他任何您想要的東西,只要您維護了實現者和客戶端之間的抽象屏障並適當地封裝了您的實現,那麼您就可以更改它而無需要求客戶端更改任何程式碼。既然我確信您已經成為工廠方法的狂熱擁護者,讓我們看一下如何針對Letter型別實現它
public abstract class Letter {
// Factory Method
public static Letter getLetter(char aTheLetter) {
// Like before, we do a one time check to see what kind of
// letter we are dealing with. Only this time, instead of setting
// a property to track it, we actually have a different class for each
// of the two letter types.
if (
aTheLetter == 'a' ||
aTheLetter == 'e' ||
aTheLetter == 'i' ||
aTheLetter == 'o' ||
aTheLetter == 'u' ||
aTheLetter == 'A' ||
aTheLetter == 'E' ||
aTheLetter == 'I' ||
aTheLetter == 'O' ||
aTheLetter == 'U'
) {
return new Vowel(aTheLetter);
} else {
return new Consonant(aTheLetter);
}
}
// User facing interface
// We make these methods abstract, thereby requiring all subclasses
// (actually, just all concrete subclasses, that is, non-abstract)
// to implement the methods.
public abstract boolean isVowel();
public abstract boolean isConsonant();
public abstract char getChar();
// Now we define the two concrete classes for this type,
// the ones that actually implement the type.
private static class Vowel extends Letter {
private char iTheLetter;
// Constructor
Vowel(char aTheLetter) {
this.iTheLetter = aTheLetter;
}
// Nice easy implementation of this method!
public boolean isVowel() {
return true;
}
// This one, too!
public boolean isConsonant() {
return false;
}
public char getLetter(){
return iTheLetter;
}
} // End local class Vowel
private static class Consonant extends Letter {
private char iTheLetter;
// Constructor
Consonant(char aTheLetter) {
this.iTheLetter = aTheLetter;
}
public boolean isVowel() {
return false;
}
public boolean isConsonant(){
return true;
}
public char getLetter(){
return iTheLetter;
}
} // End local class Consonant
} // End toplevel class Letter
這裡有幾點需要注意。
- 首先,您會注意到頂級類Letter是抽象的。這很好,因為您會注意到它實際上什麼也沒做,除了定義介面併為另外兩個類提供頂級容器。但是,將其設為抽象非常重要(不僅僅是還可以),因為我們不希望人們嘗試直接例項化Letter類。當然,我們可以透過建立一個私有建構函式來解決這個問題,但改為使類抽象更簡潔,並且使Letter類不應該被例項化更加明顯。如前所述,它還允許我們定義工作類需要實現的使用者面向介面。
- 我們建立的兩個巢狀類稱為區域性類,這與內部類基本相同,只是區域性類是靜態的,而內部類不是。它們必須是靜態的,這樣我們的靜態工廠方法才能建立它們。如果它們是非靜態的(即動態的),那麼它們只能透過Letter類的例項訪問,而我們永遠無法擁有Letter類的例項,因為Letter是抽象的。還要注意(無論如何,在 Java 中),內部類和區域性類的欄位通常使用“i”(表示內部)字首,而不是頂級類使用的“f”(表示欄位)字首。這僅僅是許多 Java 程式設計師使用的命名約定,不會真正影響程式。
- 實現Letter資料型別的兩個巢狀類實際上不必是區域性/內部類。它們可以很容易地成為擴充套件抽象Letter類的頂級類。但是,這與工廠模式的要點背道而馳,工廠模式的要點是封裝。在 Java 中,頂級類不能是私有的,因為這樣做沒有任何意義(它們對誰是私有的?),而整個要點是,沒有哪個客戶端必須(或者說實際上應該)知道型別是如何實現的。使這些類成為頂級類,允許客戶端潛在地偶然發現它們,更糟糕的是,例項化它們,完全繞過工廠模式。
- 最後,這並不是很好的程式碼。有很多方法可以使它變得更好,以真正說明工廠模式的力量。我將簡要討論這些重構,然後展示上面程式碼的另一個更完善的版本,其中包含許多重構。
重構工廠模式
請注意,這兩個區域性類在某些地方做著相同的事情。這是冗餘程式碼,不僅編寫起來需要更多工作,而且在面向物件程式設計中也極度不建議這樣做(部分原因是因為編寫起來需要更多工作,但主要是因為它更難維護,更容易出錯,例如,您在程式碼中找到一個錯誤,並在一個地方更改它,但忘記了在另一個地方更改它。)以下是上面程式碼中冗餘的列表
- 欄位iTheLetter
- 方法getLetter()
- 每個內部類的建構函式都做著相同的事情。
此外,正如我們在上面發現的,isVowel() 和 isConsonant() 恰好始終為給定例項返回彼此的相反值。但是,由於這對於這個特定示例來說有點特殊,所以我們不用擔心。從我們這樣做中得到的教訓將在 getLetter() 方法的重構中涵蓋。好的,所以在兩個類中我們有冗餘程式碼。如果您熟悉抽象過程,那麼這可能是一個您熟悉的情況。通常,在兩個不同的類中存在冗餘程式碼,使它們成為抽象的最佳選擇,這意味著將建立一個新的抽象類來實現冗餘程式碼,而這兩個類只需擴充套件這個新的抽象類,而不是實現冗餘程式碼。您知道嗎?我們已經有一個抽象的超類,我們的冗餘類共用它。我們所要做的就是讓超類實現冗餘程式碼,並且其他類將自動繼承此實現,只要我們不覆蓋它即可。因此,這對於 getLetter() 方法來說很好,我們可以將方法和 iTheLetter 欄位都移到抽象父類中。但是建構函式呢?好吧,我們的建構函式帶有一個引數,所以我們不會自動繼承它,這是 Java 的工作方式。但是我們可以使用 super 關鍵字自動委託給超類的建構函式。換句話說,我們將實現超類中的建構函式,因為欄位就在那裡,而另外兩個類將在它們自己的建構函式中委託給此方法。對於我們的示例,這並沒有節省太多工作,我們用一行對 super() 的呼叫替換了一行賦值,但在理論上,建構函式中可能存在數百行程式碼,向上移動它可能會有很大幫助。在這一點上,您可能有點擔心在 Letter 類中放入建構函式。我之前不是已經說過不要這樣做嗎?我認為我們不希望人們嘗試直接例項化 Letter?別擔心,該類仍然是抽象的。即使存在具體的建構函式,Java 也不會允許您例項化抽象類,因為它抽象,它可能具有可訪問但未定義的方法,並且它不知道如果呼叫了這種方法該怎麼辦。因此將建構函式放在其中是可以的。完成上述重構後,我們的程式碼現在如下所示
public abstract class Letter {
// Factory Method
public static Letter getLetter(char aTheLetter){
if (
aTheLetter == 'a' ||
aTheLetter == 'e' ||
aTheLetter == 'i' ||
aTheLetter == 'o' ||
aTheLetter == 'u' ||
aTheLetter == 'A' ||
aTheLetter == 'E' ||
aTheLetter == 'I' ||
aTheLetter == 'O' ||
aTheLetter == 'U'
) {
return new Vowel(aTheLetter);
} else {
return new Consonant(aTheLetter);
}
}
// Our new abstracted field. We'll make it protected so that subclasses can see it,
// and we rename it from "i" to "f", following our naming convention.
protected char fTheLetter;
// Our new constructor. It can't actually be used to instantiate an instance
// of Letter, but our sub classes can invoke it with super
protected Letter(char aTheLetter) {
this.fTheLetter = aTheLetter;
}
// The new method we're abstracting up to remove redundant code in the sub classes
public char getChar() {
return this.fTheLetter;
}
// Same old abstract methods that define part of our client facing interface
public abstract boolean isVowel();
public abstract boolean isConsonant();
// The local subclasses with the redundant code moved up.
private static class Vowel extends Letter {
// Constructor delegates to the super constructor
Vowel(char aTheLetter) {
super(aTheLetter);
}
// Still need to implement the abstract methods
public boolean isVowel() {
return true;
}
public boolean isConsonant(){
return false;
}
} // End local class Vowel
private static class Consonant extends Letter {
Consonant(char aTheLetter){
super(aTheLetter);
}
public boolean isVowel(){
return false;
}
public boolean isConsonant(){
return true;
}
} // End local class Consonant
} // End toplevel class Letter
請注意,我們已將抽象欄位設為受保護的。在本例中,這並非嚴格必要,我們可以將其保留為私有的,因為子類實際上不需要訪問它。一般來說,我更喜歡將事物設為受保護的而不是私有的,因為正如我提到的,您永遠無法真正確定專案在未來會走向何方,並且您可能不想不必要地限制未來的實施者(包括您自己)。但是,許多人更喜歡預設使用私有,並且只有在知道必要時才使用受保護的。其主要原因是 Java 中 protected 的相當特殊且有些意想不到的含義,它不僅允許子類,還允許同一包中的任何內容訪問它。這有點離題,但我認為這是一個良好的 Java 程式設計師應該瞭解的相當重要的爭論。
工廠模式和引數化多型性
Java 虛擬機器 5.0 版本引入了名為引數化多型性的東西,它在其他語言中也有許多其他名稱,包括 C++ 中的“泛型型別”。為了真正理解本節的其餘部分,您應該先閱讀該部分。但基本上,這意味著您可以在類中引入額外的引數——在例項化時設定的引數——它們定義了類中某些元素的型別,例如欄位或方法返回值。這是一個非常強大的工具,允許程式設計師避免許多那些令人討厭的 instanceof 和縮窄轉換。但是,此裝置在 JVM 中的實現並不促進工廠模式的使用,並且兩者不能很好地協同工作。這是因為 Java 不允許像型別那樣對方法進行引數化,因此您無法透過方法動態地對例項進行引數化,而只能透過使用 new 運算子。例如,假設一個型別 Foo,它使用一個稱為 T 的單個型別進行引數化。在 Java 中,我們將這樣編寫此類
class Foo<T> {
} // End class Foo
現在,我們可以擁有由各種型別引數化的 Foo 例項,例如
Foo<String> fooOverString = new Foo<String>();
Foo<Integer> fooOverInteger = new Foo<Integer>();
但是,假設我們要對 Foo 使用工廠模式。我們該怎麼做呢?您可以為要引數化的每種型別建立一個不同的工廠方法,例如
class Foo<T> {
static Foo<String> getFooOverString() {
return new Foo<String>();
}
static Foo<Integer> getFooOverInteger() {
return new Foo<Integer>();
}
} // End class Foo
但是 ArrayList 類(在 java.util 包中)之類的呢?在與 5.0 一起釋出的 Java 標準庫中,ArrayList 被引數化以定義儲存在其中的物件的型別。我們當然不想透過為每種型別編寫一個工廠方法來限制它可以被引數化的型別。這通常是引數化型別的情況:您不知道使用者想要使用哪些型別進行引數化,也不想限制它們,因此工廠模式不適合這種情況。您可以以泛型形式例項化引數化型別,這意味著您根本不指定引數,只需按照 5.0 之前的例項化方式例項化它即可。但這迫使您放棄引數化。以下是如何使用泛型進行操作
class Foo<T> {
public static <E> Foo<E> getFoo() {
return new Foo<E>();
}
} // End class Foo
示例
在 Java 中,實現 java.sql.Connection 的類是語句的工廠。透過呼叫 createStatement() 方法,您可以建立一個語句,您只知道它的介面。工廠會為您選擇合適的例項類。
成本
此模式在正確的時間實施時並不昂貴。如果您必須重構現有程式碼,它可能會更昂貴。
建立
它的實現很容易,而且沒有額外的成本(它與沒有此模式的實現相比並不更昂貴)。
維護
沒有額外的成本或額外的約束。
移除
此模式可以輕鬆刪除,因為自動重構操作可以輕鬆刪除其存在。
良好實踐
- 用例項化類在名字首和 factory 術語在後綴中命名工廠類,以向其他開發人員表明模式的使用。
- 避免將工廠結果(在資料庫中)持久化到工廠類中,以遵守 SOLID 原則並允許建立短暫物件,例如在自動測試中。
實施
REPORT zz_pizza_factory_test NO STANDARD PAGE HEADING .
TYPES ty_pizza_type TYPE i .
*----------------------------------------------------------------------*
* CLASS lcl_pizza DEFINITION
*----------------------------------------------------------------------*
CLASS lcl_pizza DEFINITION ABSTRACT .
PUBLIC SECTION .
DATA p_pizza_name TYPE string .
METHODS get_price ABSTRACT
RETURNING value(y_price) TYPE i .
ENDCLASS . "lcl_pizza DEFINITION
*----------------------------------------------------------------------*
* CLASS lcl_ham_and_mushroom_pizza DEFINITION
*----------------------------------------------------------------------*
CLASS lcl_ham_and_mushroom_pizza DEFINITION INHERITING FROM lcl_pizza .
PUBLIC SECTION .
METHODS constructor .
METHODS get_price REDEFINITION .
ENDCLASS . "lcl_ham_and_mushroom_pizza DEFINITION
*----------------------------------------------------------------------*
* CLASS lcl_deluxe_pizza DEFINITION
*----------------------------------------------------------------------*
CLASS lcl_deluxe_pizza DEFINITION INHERITING FROM lcl_pizza .
PUBLIC SECTION .
METHODS constructor .
METHODS get_price REDEFINITION .
ENDCLASS . "lcl_ham_and_mushroom_pizza DEFINITION
*----------------------------------------------------------------------*
* CLASS lcl_hawaiian_pizza DEFINITION
*----------------------------------------------------------------------*
CLASS lcl_hawaiian_pizza DEFINITION INHERITING FROM lcl_pizza .
PUBLIC SECTION .
METHODS constructor .
METHODS get_price REDEFINITION .
ENDCLASS . "lcl_ham_and_mushroom_pizza DEFINITION
*----------------------------------------------------------------------*
* CLASS lcl_pizza_factory DEFINITION
*----------------------------------------------------------------------*
CLASS lcl_pizza_factory DEFINITION .
PUBLIC SECTION .
CONSTANTS: BEGIN OF co_pizza_type ,
ham_mushroom TYPE ty_pizza_type VALUE 1 ,
deluxe TYPE ty_pizza_type VALUE 2 ,
hawaiian TYPE ty_pizza_type VALUE 3 ,
END OF co_pizza_type .
CLASS-METHODS create_pizza IMPORTING x_pizza_type TYPE ty_pizza_type
RETURNING value(yo_pizza) TYPE REF TO lcl_pizza
EXCEPTIONS ex_invalid_pizza_type .
ENDCLASS . "lcl_pizza_factory DEFINITION
*----------------------------------------------------------------------*
* CLASS lcl_ham_and_mushroom_pizza
*----------------------------------------------------------------------*
CLASS lcl_ham_and_mushroom_pizza IMPLEMENTATION .
METHOD constructor .
super->constructor( ) .
p_pizza_name = 'Ham & Mushroom Pizza'(001) .
ENDMETHOD . "constructor
METHOD get_price .
y_price = 850 .
ENDMETHOD . "get_price
ENDCLASS . "lcl_ham_and_mushroom_pizza IMPLEMENTATION
*----------------------------------------------------------------------*
* CLASS lcl_deluxe_pizza IMPLEMENTATION
*----------------------------------------------------------------------*
CLASS lcl_deluxe_pizza IMPLEMENTATION .
METHOD constructor .
super->constructor( ) .
p_pizza_name = 'Deluxe Pizza'(002) .
ENDMETHOD . "constructor
METHOD get_price .
y_price = 1050 .
ENDMETHOD . "get_price
ENDCLASS . "lcl_deluxe_pizza IMPLEMENTATION
*----------------------------------------------------------------------*
* CLASS lcl_hawaiian_pizza IMPLEMENTATION
*----------------------------------------------------------------------*
CLASS lcl_hawaiian_pizza IMPLEMENTATION .
METHOD constructor .
super->constructor( ) .
p_pizza_name = 'Hawaiian Pizza'(003) .
ENDMETHOD . "constructor
METHOD get_price .
y_price = 1150 .
ENDMETHOD . "get_price
ENDCLASS . "lcl_hawaiian_pizza IMPLEMENTATION
*----------------------------------------------------------------------*
* CLASS lcl_pizza_factory IMPLEMENTATION
*----------------------------------------------------------------------*
CLASS lcl_pizza_factory IMPLEMENTATION .
METHOD create_pizza .
CASE x_pizza_type .
WHEN co_pizza_type-ham_mushroom .
CREATE OBJECT yo_pizza TYPE lcl_ham_and_mushroom_pizza .
WHEN co_pizza_type-deluxe .
CREATE OBJECT yo_pizza TYPE lcl_deluxe_pizza .
WHEN co_pizza_type-hawaiian .
CREATE OBJECT yo_pizza TYPE lcl_hawaiian_pizza .
ENDCASE .
ENDMETHOD . "create_pizza
ENDCLASS . "lcl_pizza_factory IMPLEMENTATION
START-OF-SELECTION .
DATA go_pizza TYPE REF TO lcl_pizza .
DATA lv_price TYPE i .
DO 3 TIMES .
go_pizza = lcl_pizza_factory=>create_pizza( sy-index ) .
lv_price = go_pizza->get_price( ) .
WRITE:/ 'Price of', go_pizza->p_pizza_name, 'is £', lv_price LEFT-JUSTIFIED .
ENDDO .
*Output:
*Price of Ham & Mushroom Pizza is £ 850
*Price of Deluxe Pizza is £ 1.050
*Price of Hawaiian Pizza is £ 1.150
public class Pizza
{
protected var _price:Number;
public function get price():Number
{
return _price;
}
}
public class HamAndMushroomPizza extends Pizza
{
public function HamAndMushroomPizza()
{
_price = 8.5;
}
}
public class DeluxePizza extends Pizza
{
public function DeluxePizza()
{
_price = 10.5;
}
}
public class HawaiianPizza extends Pizza
{
public function HawaiianPizza()
{
_price = 11.5;
}
}
public class PizzaFactory
{
static public function createPizza(type:String):Pizza
{
switch (type)
{
case "HamAndMushroomPizza":
return new HamAndMushroomPizza();
break;
case "DeluxePizza":
return new DeluxePizza();
break;
case "HawaiianPizza":
return new HawaiianPizza();
break;
default:
throw new ArgumentError("The pizza type " + type + " is not recognized.");
}
}
}
public class Main extends Sprite
{
public function Main()
{
for each (var pizza:String in ["HamAndMushroomPizza", "DeluxePizza", "HawaiianPizza"])
{
trace("Price of " + pizza + " is " + PizzaFactory.createPizza(pizza).price);
}
}
}
Output:
Price of HamAndMushroomPizza is 8.5
Price of DeluxePizza is 10.5
Price of HawaiianPizza is 11.5
此 C++11 實現基於該書中 C++98 之前的實現。
#include <iostream>
enum Direction {North, South, East, West};
class MapSite {
public:
virtual void enter() = 0;
virtual ~MapSite() = default;
};
class Room : public MapSite {
public:
Room() :roomNumber(0) {}
Room(int n) :roomNumber(n) {}
void setSide(Direction d, MapSite* ms) {
std::cout << "Room::setSide " << d << ' ' << ms << '\n';
}
virtual void enter() {}
Room(const Room&) = delete; // rule of three
Room& operator=(const Room&) = delete;
private:
int roomNumber;
};
class Wall : public MapSite {
public:
Wall() {}
virtual void enter() {}
};
class Door : public MapSite {
public:
Door(Room* r1 = nullptr, Room* r2 = nullptr)
:room1(r1), room2(r2) {}
virtual void enter() {}
Door(const Door&) = delete; // rule of three
Door& operator=(const Door&) = delete;
private:
Room* room1;
Room* room2;
};
class Maze {
public:
void addRoom(Room* r) {
std::cout << "Maze::addRoom " << r << '\n';
}
Room* roomNo(int) const {
return nullptr;
}
};
// If createMaze calls virtual functions instead of constructor calls to create the rooms, walls, and doors it requires, then you can change the classes that get instantiated by making a subclass of MazeGame and redefining those virtual functions. This approach is an example of the Factory Method (121) pattern.
class MazeGame {
public:
Maze* createMaze () {
Maze* aMaze = makeMaze();
Room* r1 = makeRoom(1);
Room* r2 = makeRoom(2);
Door* theDoor = makeDoor(r1, r2);
aMaze->addRoom(r1);
aMaze->addRoom(r2);
r1->setSide(North, makeWall());
r1->setSide(East, theDoor);
r1->setSide(South, makeWall());
r1->setSide(West, makeWall());
r2->setSide(North, makeWall());
r2->setSide(East, makeWall());
r2->setSide(South, makeWall());
r2->setSide(West, theDoor);
return aMaze;
}
// factory methods:
virtual Maze* makeMaze() const {
return new Maze;
}
virtual Room* makeRoom(int n) const {
return new Room(n);
}
virtual Wall* makeWall() const {
return new Wall;
}
virtual Door* makeDoor(Room* r1, Room* r2) const {
return new Door(r1, r2);
}
virtual ~MazeGame() = default;
};
int main() {
MazeGame game;
game.createMaze();
}
程式輸出如下
Maze::addRoom 0xcaced0
Maze::addRoom 0xcacef0
Room::setSide 0 0xcad340
Room::setSide 2 0xcacf10
Room::setSide 1 0xcad360
Room::setSide 3 0xcad380
Room::setSide 0 0xcad3a0
Room::setSide 2 0xcad3c0
Room::setSide 1 0xcad3e0
Room::setSide 3 0xcacf10
在 Common Lisp 中,工廠方法實際上並不需要,因為類和類名是一流的值。
(defclass pizza ()
((price :accessor price)))
(defclass ham-and-mushroom-pizza (pizza)
((price :initform 850)))
(defclass deluxe-pizza (pizza)
((price :initform 1050)))
(defclass hawaiian-pizza (pizza)
((price :initform 1150)))
(defparameter *pizza-types*
(list 'ham-and-mushroom-pizza
'deluxe-pizza
'hawaiian-pizza))
(loop for pizza-type in *pizza-types*
do (format t "~%Price of ~a is ~a"
pizza-type
(price (make-instance pizza-type))))
Output:
Price of HAM-AND-MUSHROOM-PIZZA is 850
Price of DELUXE-PIZZA is 1050
Price of HAWAIIAN-PIZZA is 1150
program FactoryMethod;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
// Product
TProduct = class(TObject)
public
function GetName(): string; virtual; abstract;
end;
// ConcreteProductA
TConcreteProductA = class(TProduct)
public
function GetName(): string; override;
end;
// ConcreteProductB
TConcreteProductB = class(TProduct)
public
function GetName(): string; override;
end;
// Creator
TCreator = class(TObject)
public
function FactoryMethod(): TProduct; virtual; abstract;
end;
// ConcreteCreatorA
TConcreteCreatorA = class(TCreator)
public
function FactoryMethod(): TProduct; override;
end;
// ConcreteCreatorB
TConcreteCreatorB = class(TCreator)
public
function FactoryMethod(): TProduct; override;
end;
{ ConcreteProductA }
function TConcreteProductA.GetName(): string;
begin
Result := 'ConcreteProductA';
end;
{ ConcreteProductB }
function TConcreteProductB.GetName(): string;
begin
Result := 'ConcreteProductB';
end;
{ ConcreteCreatorA }
function TConcreteCreatorA.FactoryMethod(): TProduct;
begin
Result := TConcreteProductA.Create();
end;
{ ConcreteCreatorB }
function TConcreteCreatorB.FactoryMethod(): TProduct;
begin
Result := TConcreteProductB.Create();
end;
const
Count = 2;
var
Creators: array[1..Count] of TCreator;
Product: TProduct;
I: Integer;
begin
// An array of creators
Creators[1] := TConcreteCreatorA.Create();
Creators[2] := TConcreteCreatorB.Create();
// Iterate over creators and create products
for I := 1 to Count do
begin
Product := Creators[I].FactoryMethod();
WriteLn(Product.GetName());
Product.Free();
end;
for I := 1 to Count do
Creators[I].Free();
ReadLn;
end.
帶有披薩的示例
abstract class Pizza {
public abstract int getPrice(); // Count the cents
}
class HamAndMushroomPizza extends Pizza {
public int getPrice() {
return 850;
}
}
class DeluxePizza extends Pizza {
public int getPrice() {
return 1050;
}
}
class HawaiianPizza extends Pizza {
public int getPrice() {
return 1150;
}
}
class PizzaFactory {
public enum PizzaType {
HamMushroom,
Deluxe,
Hawaiian
}
public static Pizza createPizza(PizzaType pizzaType) {
switch (pizzaType) {
case HamMushroom:
return new HamAndMushroomPizza();
case Deluxe:
return new DeluxePizza();
case Hawaiian:
return new HawaiianPizza();
}
throw new IllegalArgumentException("The pizza type " + pizzaType + " is not recognized.");
}
}
class PizzaLover {
/**
* Create all available pizzas and print their prices
*/
public static void main (String args[]) {
for (PizzaFactory.PizzaType pizzaType : PizzaFactory.PizzaType.values()) {
System.out.println("Price of " + pizzaType + " is " + PizzaFactory.createPizza(pizzaType).getPrice());
}
}
}
輸出
Price of HamMushroom is 850 Price of Deluxe is 1050 Price of Hawaiian is 1150
帶有影像的另一個示例
- 抽象建立者
- 建立產品的介面。
package mypkg;
import java.awt.image.BufferedImage;
import java.io.IOException;
/**
*
* @author xxx
*/
public interface PhotoReader {
public BufferedImage getImage() throws IOException;
}
- 具體建立者
- 一個類來建立特定的產品。
package mypkg;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
/**
*
* @author xxx
*/
public class JPEGReader implements PhotoReader {
ImageReader reader;
File jpegFile;
ImageInputStream iis;
public JPEGReader(String filePath) throws IOException {
jpegFile = new File(filePath);
iis = ImageIO.createImageInputStream(jpegFile);
Iterator readers = ImageIO.getImageReadersByFormatName("jpg");
reader = (ImageReader)readers.next();
this.reader.setInput(iis, true);
}
public BufferedImage getImage() throws IOException {
return reader.read(0);
}
- 工廠類
- 一個類,在執行時返回特定的具體建立者以建立產品。
package mypkg;
import java.io.IOException;
/**
*
* @author xxx
*/
public class PhotoReaderFactory {
enum Mimi {
jpg, JPG, gif, GIF, bmp, BMP, png, PNG
};
public static PhotoReader getPhotoReader(String filePath) {
String suffix = getFileSuffix(filePath);
PhotoReader reader = null;
try {
switch (Mimi.valueOf(suffix)) {
case jpg :
case JPG : reader = new JPEGReader(filePath); break;
case gif :
case GIF : reader = new GIFReader(filePath); break;
case bmp :
case BMP : reader = new BMPReader(filePath); break;
case png :
case PNG : reader = new PNGReader(filePath); break;
default : break;
}
} catch(IOException io) {
io.printStackTrace();
}
return reader;
}
private static String getFileSuffix(String filePath) {
String[] stringArray = filePath.split("\\.");
return stringArray[stringArray.length - 1];
}
}
此 JavaScript 示例使用 Firebug 控制檯輸出資訊。
/**
* Extends parent class with child. In Javascript, the keyword "extends" is not
* currently implemented, so it must be emulated.
* Also it is not recommended to use keywords for future use, so we name this
* function "extends" with capital E. Javascript is case-sensitive.
*
* @param function parent constructor function
* @param function (optional) used to override default child constructor function
*/
function Extends(parent, childConstructor) {
var F = function () {};
F.prototype = parent.prototype;
var Child = childConstructor || function () {};
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.parent = parent.prototype;
// return instance of new object
return Child;
}
/**
* Abstract Pizza object constructor
*/
function Pizza() {
throw new Error('Cannot instatiate abstract object!');
}
Pizza.prototype.price = 0;
Pizza.prototype.getPrice = function () {
return this.price;
}
var HamAndMushroomPizza = Extends(Pizza);
HamAndMushroomPizza.prototype.price = 8.5;
var DeluxePizza = Extends(Pizza);
DeluxePizza.prototype.price = 10.5;
var HawaiianPizza = Extends(Pizza);
HawaiianPizza.prototype.price = 11.5;
var PizzaFactory = {
createPizza: function (type) {
var baseObject = 'Pizza';
var targetObject = type.charAt(0).toUpperCase() + type.substr(1);
if (typeof window[targetObject + baseObject] === 'function') {
return new window[targetObject + baseObject];
}
else {
throw new Error('The pizza type ' + type + ' is not recognized.');
}
}
};
//var price = PizzaFactory.createPizza('deluxe').getPrice();
var pizzas = ['HamAndMushroom', 'Deluxe', 'Hawaiian'];
for (var i in pizzas) {
console.log('Price of ' + pizzas[i] + ' is ' + PizzaFactory.createPizza(pizzas[i]).getPrice());
}
輸出
Price of HamAndMushroom is 8.50 Price of Deluxe is 10.50 Price of Hawaiian is 11.50
class Pizza a where
price :: a -> Float
data HamMushroom = HamMushroom
data Deluxe = Deluxe
data Hawaiian = Hawaiian
instance Pizza HamMushroom where
price _ = 8.50
instance Pizza Deluxe where
price _ = 10.50
instance Pizza Hawaiian where
price _ = 11.50
用法示例
main = print (price Hawaiian)
package Pizza;
use Moose;
has price => (is => "rw", isa => "Num", builder => "_build_price" );
package HamAndMushroomPizza;
use Moose; extends "Pizza";
sub _build_price { 8.5 }
package DeluxePizza;
use Moose; extends "Pizza";
sub _build_price { 10.5 }
package HawaiianPizza;
use Moose; extends "Pizza";
sub _build_price { 11.5 }
package PizzaFactory;
sub create {
my ( $self, $type ) = @_;
return ($type . "Pizza")->new;
}
package main;
for my $type ( qw( HamAndMushroom Deluxe Hawaiian ) ) {
printf "Price of %s is %.2f\n", $type, PizzaFactory->create( $type )->price;
}
對於 Perl 中的此示例,工廠實際上並不需要,並且可以更簡潔地編寫
package Pizza;
use Moose;
has price => (is => "rw", isa => "Num", builder => "_build_price" );
package HamAndMushroomPizza;
use Moose; extends "Pizza";
sub _build_price { 8.5 }
package DeluxePizza;
use Moose; extends "Pizza";
sub _build_price { 10.5 }
package HawaiianPizza;
use Moose; extends "Pizza";
sub _build_price { 11.5 }
package main;
for my $type ( qw( HamAndMushroom Deluxe Hawaiian ) ) {
printf "Price of %s is %.2f\n", $type, ($type . "Pizza")->new->price;
}
輸出
Price of HamAndMushroom is 8.50 Price of Deluxe is 10.50 Price of Hawaiian is 11.50
<?php
abstract class Pizza
{
protected $_price;
public function getPrice()
{
return $this->_price;
}
}
class HamAndMushroomPizza extends Pizza
{
protected $_price = 8.5;
}
class DeluxePizza extends Pizza
{
protected $_price = 10.5;
}
class HawaiianPizza extends Pizza
{
protected $_price = 11.5;
}
class PizzaFactory
{
public static function createPizza($type)
{
$baseClass = 'Pizza';
$targetClass = ucfirst($type).$baseClass;
if (class_exists($targetClass) && is_subclass_of($targetClass, $baseClass))
return new $targetClass;
else
throw new Exception("The pizza type '$type' is not recognized.");
}
}
$pizzas = array('HamAndMushroom','Deluxe','Hawaiian');
foreach($pizzas as $p) {
printf(
"Price of %s is %01.2f".PHP_EOL ,
$p ,
PizzaFactory::createPizza($p)->getPrice()
);
}
// Output:
// Price of HamAndMushroom is 8.50
// Price of Deluxe is 10.50
// Price of Hawaiian is 11.50
?>
#
# Pizza
#
class Pizza(object):
def __init__(self):
self._price = None
def get_price(self):
return self._price
class HamAndMushroomPizza(Pizza):
def __init__(self):
self._price = 8.5
class DeluxePizza(Pizza):
def __init__(self):
self._price = 10.5
class HawaiianPizza(Pizza):
def __init__(self):
self._price = 11.5
#
# PizzaFactory
#
class PizzaFactory(object):
@staticmethod
def create_pizza(pizza_type):
if pizza_type == 'HamMushroom':
return HamAndMushroomPizza()
elif pizza_type == 'Deluxe':
return DeluxePizza()
elif pizza_type == 'Hawaiian':
return HawaiianPizza()
if __name__ == '__main__':
for pizza_type in ('HamMushroom', 'Deluxe', 'Hawaiian'):
print 'Price of {0} is {1}'.format(pizza_type, PizzaFactory.create_pizza(pizza_type).get_price())
與 Perl、Common Lisp 和其他動態語言一樣,上述型別的工廠實際上並不必要,因為類是一流的物件,可以直接傳遞,從而導致以下更自然的版本
#
# Pizza
#
class Pizza(object):
def __init__(self):
self._price = None
def get_price(self):
return self._price
class HamAndMushroomPizza(Pizza):
def __init__(self):
self._price = 8.5
class DeluxePizza(Pizza):
def __init__(self):
self._price = 10.5
class HawaiianPizza(Pizza):
def __init__(self):
self._price = 11.5
if __name__ == '__main__':
for pizza_class in (HamAndMushroomPizza, DeluxePizza, HawaiianPizza):
print('Price of {0} is {1}'.format(pizza_class.__name__, pizza_class().get_price()))
請注意,上述類本身只是用作值,這pizza_class迭代。該類只是透過將其視為函式來建立。在這種情況下,如果pizza_class儲存一個類,那麼pizza_class()建立一個該類的新的物件。另一種編寫最後子句的方法,它更接近原始示例並使用字串而不是類物件,如下所示
if __name__ == '__main__':
for pizza_type in ('HamAndMushroom', 'Deluxe', 'Hawaiian'):
print 'Price of {0} is {1}'.format(pizza_type, eval(pizza_type + 'Pizza')().get_price())
在這種情況下,正確的類名透過新增'Pizza',以及eval被呼叫以將其轉換為類物件。
Imports System
Namespace FactoryMethodPattern
Public Class Program
Shared Sub Main()
OutputPizzaFactory(New LousPizzaStore())
OutputPizzaFactory(New TonysPizzaStore())
Console.ReadKey()
End Sub
Private Shared Sub OutputPizzaFactory(ByVal factory As IPizzaFactory)
Console.WriteLine("Welcome to {0}", factory.Name)
For Each p As Pizza In factory.CreatePizzas
Console.WriteLine(" {0} - ${1} - {2}", p.GetType().Name, p.Price, p.Toppings)
Next
End Sub
End Class
Public MustInherit Class Pizza
Protected _toppings As String
Protected _price As Decimal
Public ReadOnly Property Toppings() As String
Get
Return _toppings
End Get
End Property
Public ReadOnly Property Price() As Decimal
Get
Return _price
End Get
End Property
Public Sub New(ByVal __price As Decimal)
_price = __price
End Sub
End Class
Public Interface IPizzaFactory
ReadOnly Property Name() As String
Function CreatePizzas() As Pizza()
End Interface
Public Class Pepperoni
Inherits Pizza
Public Sub New(ByVal price As Decimal)
MyBase.New(price)
_toppings = "Cheese, Pepperoni"
End Sub
End Class
Public Class Cheese
Inherits Pizza
Public Sub New(ByVal price As Decimal)
MyBase.New(price)
_toppings = "Cheese"
End Sub
End Class
Public Class LousSpecial
Inherits Pizza
Public Sub New(ByVal price As Decimal)
MyBase.New(price)
_toppings = "Cheese, Pepperoni, Ham, Lou's Special Sauce"
End Sub
End Class
Public Class TonysSpecial
Inherits Pizza
Public Sub New(ByVal price As Decimal)
MyBase.New(price)
_toppings = "Cheese, Bacon, Tomatoes, Tony's Special Sauce"
End Sub
End Class
Public Class LousPizzaStore
Implements IPizzaFactory
Public Function CreatePizzas() As Pizza() Implements IPizzaFactory.CreatePizzas
Return New Pizza() {New Pepperoni(6.99D), New Cheese(5.99D), New LousSpecial(7.99D)}
End Function
Public ReadOnly Property Name() As String Implements IPizzaFactory.Name
Get
Return "Lou's Pizza Store"
End Get
End Property
End Class
Public Class TonysPizzaStore
Implements IPizzaFactory
Public Function CreatePizzas() As Pizza() Implements IPizzaFactory.CreatePizzas
Return New Pizza() {New Pepperoni(6.5D), New Cheese(5.5D), New TonysSpecial(7.5D)}
End Function
Public ReadOnly Property Name() As String Implements IPizzaFactory.Name
Get
Return "Tony's Pizza Store"
End Get
End Property
End Class
End Namespace
Output:
Welcome to Lou's Pizza Store
Pepperoni - $6.99 - Cheese, Pepperoni
Cheese - $5.99 - Cheese
LousSpecial - $7.99 - Cheese, Pepperoni, Ham, Lou's Special Sauce
Welcome to Tony's Pizza Store
Pepperoni - $6.5 - Cheese, Pepperoni
Cheese - $5.5 - Cheese
TonysSpecial - $7.5 - Cheese, Bacon, Tomatoes, Tony's Special Sauce
