文章目录
工厂方法(Factory Method)模式
隶属类别——对象创建型模式
1. 意图
定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类实例化延迟到其子类
2. 别名
虚构造器(Virtual Constructor)
3. 动机
框架使用抽象类定义和维护对象之间的关系。这些对象的创建通常也由框架负责。
考虑这样一个应用框架,它可以向用户显示多个文档。在这个框架中,两个主要的抽象类Application和Document。这两个类都是抽象的,用户必须通过它们的子类来做与具体应用相关的实现。例如,为创建一个绘图应用,我们定义类DrawingApplication和DrawingDocument。Applicationle类负责应用管理Document并根据需要创建它们——例如,当用户从菜单中选择Open或New的时候。
因为被实例化的特定Document子类是与特定应用相关的,所以Application类不可能预测到哪个Document子类将被实例化——Application类仅知道一个新的文档如何应被创建。而不知道哪一种Document将被创建。这就产生了一个尴尬的局面:框架必须实例化类,但是它只知道无法被实例化的抽象类。
Factory Method模式提供了一个解决方案。它封装了哪个Document子类将被创建的信息将这些信息从该框架中分离出来,如下图所示
Application的子类重定义Application的抽象操作CreateDocument以返回适当的Document子类对象。一旦一个子类Application对象实例化以后,它就可以实例化与应用相关的文档,而需知道这些文档的类。我们称CreateDocument是一个工厂方法(Factory Method),因为它负责“生产”一个对象。
4. 适用性
在下列情况下可以使用Factory Method模式:
- 当一个类不知道它所必须创建的对象的类的时候
- 当一个类希望由它的子类来指定它创建的对象的时候
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化(只有子类具体知道,其父类不知道)的时候。
5. 结构
6. 参与者
-
Product(Document)
——定义工厂方法创建的对象的接口(泛指不一定是真正的接口)。
-
ConcreteProduct(MyDocument)
——实现Product接口
-
Creator(Application)
——声明工厂方法,该方法返回一个Product类型的对象。Creator也可以定义一个工厂方法的缺省实现,它返回一个缺省的ConcreteProduct对象。
——可以调用工厂方法以创建一个Product对象
-
ConcreteCreator(MyApplication)
——重定义(或者说实现)工厂方法以返回一个ConcreteProduct实例
7. 协作
- Creator依赖于它的子类来定义工厂方法,所以它返回一个适当的ConcreteProduct的实例
8. 效果
Factory Method有以下的优点和缺点:
优点:
-
1.工厂方法不再将于特定应用有关的类绑定到你的代码中。代码仅处理Product接口,因此它可以与用户定义的任何ConcreteProduct类一起使用。
-
2.为子类提供挂钩(hook) 用工厂方法在一个类的内部创建对象通常比直接创建对象更灵活。Factory Method给子类一个挂钩以提供对象的扩展版本。
-
3.连接平行的类层次 迄今为止,我们所考虑的例子中,工厂方法并不往往只是被Creator调用,客户可以找到一些有用的工厂方法。尤其在平行类层次的情况下。
当一个类将它的一些职责委托给一个独立的类的时候,就产生了平行类层次。考虑可以被交互操作的图形,也就是说,它们可以用鼠标进行伸展,移动,或者旋转。实现这样一些交互并不总是那么容易,它通常需要存储和更新在给定时刻记录状态信息,这个状态仅仅在操纵时需要。因此它不需要被保存在图形对象中,此外,当用户操作图形,不同的图形有不同的行为。例如,将直线图形拉长可能会产生一个端点被移动的效果,而伸展正文图形则可能会改变行距。
有了这些限制,最好使用一个独立的Manipulator对象实现交互并保存所需要的任何与特定操作相关的状态,不同的图形将使用不同的Manipulator(机械手)子类来处理特定的交互,得到的Manipulator类层次与Figure层次是平行(至少是部分平行的),如下图所示:
Figure类提供了一个CreateManipulator工厂方法,它使得客户可以创建一个与Figure相对应的Manipulator。Figure子类重定义实现CreateManipulator以返回一个默认的Manipulator实例。做为一种选择,Figure类可以实现CreateManipulator以返回一个默认的Manipulator实例,而Figure子类可以只是继承这个缺省实现。这样的Figure类不需要相应的Manipulator子类——因此该层次只是部分平行的。
注意工厂方法是怎样定义两个类层次之间的连接的。它将哪些类应一同工作的工作的信息局部化了。
缺点:
- 工厂方法有一个潜在的缺点在于客户可能仅仅为了创建一个特定的ConcreteProduct对象,就不得不创建Creator的子类。当Creator子类不必需时,客户现在必然要处理类演化的其他方法(比如代码示例中CreatePizza中处理type(String));但是当客户无论如何必需创建Creator的子类时,创建子类也是可行的。
9. 实现
当应用Factory Method模式时要考虑下面一些问题:
-
1.主要有两种不同的情况
- 第一种情况是,Creator类是一个抽象类并且不提供它所声明的工厂方法的实现
- 第二种情况是,Creator是一个具体的类而且为工厂方法提供了一个缺省的实现。也有可能有一个定义了缺省实现的抽象类,但这不太常见。
第一种情况需要子类来定义实现,因为没有合理的缺省实现。它避免了对不可预见类是否进行实例化的两难局面(可以什么都不干,也可以实例化,有个抉择)。在第二种情况中,具体的Creator主要因为灵活性才使用工厂方法,它所遵循的的准则是,“用一个独立的操作创建对象,这样子类才能重新定义它们的创建方式。”这条准则保证了子类的设计者能够在必要的时候改变父类所实例化的对象的类。
-
参数化工厂方法 该模式的另一个情况使得工厂方法可以创建多个产品(例如代码示例中的ChicagoPizzaStore.java)。工厂方法采用一个表示要被创建的对象种类的参数.工厂方法创建的所有对象将共享Product接口。在Document的例子中,Application可能支持不同种类的Document。你给createDocument传递一个外部参数来指定将要创建的文档类型。
图形编辑框架Unidraw[VL90]使用这种方法来重构存储在磁盘上的对象。Unidraw定义了一个Creator类,该类拥有一个以类标识符为参数的工厂方法Create()。类标识符指定要被实例化的类。当Unidraw将一个对象存盘时,它首先写类标识符,然后是它的实例变量。当它从磁盘中重构该对象时,它首先读取的是类标识符。
一旦类标识符被读取后,这个框架就将该标识符作为参数,调用create(),create()到构造器中查询相应的类并用它实例化对象。最后,Create()调用对象的read()操作,读取磁盘上剩余的信息并初始化该对象的实例变量。
重定义一个参数化的工厂方法使你可以简单而又选择性的扩展或改变一个Creator生产的产品。你可以为新产品引入新的标识符,或可以将已有的标识符和不同的产品相关联(例如新的Pizza引入新的Pizza名)。
public class ChicagoPizzaStore extends PizzaStore{ public Pizza createPizza(String type) { if (type.equals("cheese")) { return new ChicagoStyleCheesePizza(); } else if (type.equals("pepperoni")) { return new ChicagoStylePepperoniPizza(); } else if (type.equals("clam")) { return new ChicagoStyleClamPizza(); } else if (type.equals("veggie")) { return new ChicagoStyleVeggiePizza(); } else return null; } }
注意这个操作所做的最后一件事是调用父类的Create.这是因为ChicagoPizzaStore.createPizza()仅处理了"cheese",“pepperoni”,“clam”,"veggie"这种四种情况,最后相当于缺省操作或默认操作,当然,如果父类中的createPizza()有对应的缺省操作的话,可以调用super.createPizza()把创建职责延迟给父类,JDK8接口可以使用Default来实现接口的缺省的方法实现,接口中也可以写方法了!
-
3.特定语言的变化和问题 不同的语言有助于产生其他一些有趣的变化和警告(caveat)。
-
4.使用模板以避免创建子类 正如我们已经提及的,工厂方法另一个潜在的问题时它可能为了创建适当的Product对象而迫使你创建Creator子类。在C++中另一个解决办法是提供Creator的一个模板子类,它使用Product对象类作为模板参数。在Java就相当于使用泛型,可是泛型如何实例化?可不可以不使用构造器传入参数,实例化一个具体的类型 如下:
public class ConcreteCreator<T extends Product> extends Creator{ @Override public Product createProduct() { // 如何实例化泛型T?达到 return new T();的效果? } }
-
这个问题我到stackoverflow去提了,找到两个相对而言稍微靠谱一点的回答,结果如下:
-
A1:In java, there is already a generic type for factory methods. It’s
Supplier<T>
.You should probably use
Supplier<Product>
instead of yourCreator
.Then, you typically use a lambda function or method reference to supply an instance.
If you want to call
setCreator(Supplier<Product>)
, for example, and you want it to create yourMyProduct
subclass, then you just callsetCreator(MyProduct::new)
.Lambdas allow you to do more complex constructions without subclassing, even when an appropriate constructor doesn’t exist, like
setCreator(() -> new MyProduct(CONST_VAL_1, UtilityClass.getCurrentValue2());
-
A2:Si
-