工厂方法
工厂模式是一种设计模式,用于促进数据表示的封装。
- 问题
- 我们希望在运行时根据一些配置或应用程序参数来决定要创建哪个对象。当我们编写代码时,我们不知道应该实例化哪个类。
- 解决方案
- 定义一个用于创建对象的接口,但让子类决定要实例化哪个类。工厂方法允许类将实例化推迟到子类。它的主要目的是提供一种方法让用户检索具有已知编译时类型的实例,但其运行时类型实际上可能不同。换句话说,一个应该返回类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