1 前言
工厂模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
2 简介
意图:
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
主要解决:
主要解决接口选择的问题。
何时使用:
我们明确地计划不同条件下创建不同实例时。
如何解决:
让其子类实现工厂接口,返回的也是一个抽象的产品。
关键代码:
创建过程在其子类执行。
注意事项:
作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
3 概念示例
本例说明了工厂方法设计模式的结构并重点回答了下面的问题:
- 它由哪些类组成?
- 这些类扮演了哪些角色?
- 模式中的各个元素会以何种方式相互关联?
from __future__ import annotations
from abc import ABC, abstractmethod
class Creator(ABC):
"""
造物主类声明的工厂方法应该返回一个
一个产品类的对象。通常创造者的子类提供
该方法的实现。
"""
@abstractmethod
def factory_method(self):
"""
请注意,创作者也可以提供一些默认的实现
工厂方法。
"""
pass
def some_operation(self) -> str:
"""
还要注意,尽管它的名字,创建者的首要责任
不是创造产品。通常,它包含一些核心业务逻辑
依靠产品对象,返回的工厂方法。
子类可以间接地改变业务逻辑通过重写
工厂方法返回一个不同类型的产品。
"""
# Call the factory method to create a Product object.
product = self.factory_method()
# Now, use the product.
result = f"Creator: The same creator's code has just worked with {product.operation()}"
return result
"""
具体的创建者覆盖工厂方法为了改变结果
产品的类型。
"""
class ConcreteCreator1(Creator):
"""
注意,该方法的签名仍然使用抽象的产品类型,
尽管具体的产品实际上是返回的方法。这
创作者可以保持独立于具体产品类。
"""
def factory_method(self) -> Product:
return ConcreteProduct1()
class ConcreteCreator2(Creator):
def factory_method(self) -> Product:
return ConcreteProduct2()
class Product(ABC):
"""
产品接口声明的操作,所有具体的产品
必须实现。
"""
@abstractmethod
def operation(self) -> str:
pass
"""
具体的产品提供各种产品接口的实现。
"""
class ConcreteProduct1(Product):
def operation(self) -> str:
return "{Result of the ConcreteProduct1}"
class ConcreteProduct2(Product):
def operation(self) -> str:
return "{Result of the ConcreteProduct2}"
def client_code(creator: Creator) -> None:
"""
客户端代码与一个具体创建者的实例一起工作,尽管是通过它的基本接口。只要客户端通过基本接口继续使用创建者,就可以传递任何创建者的子类。
"""
print(f"Client: I'm not aware of the creator's class, but it still works.\n"
f"{creator.some_operation()}", end="")
if __name__ == "__main__":
print("App: Launched with the ConcreteCreator1.")
client_code(ConcreteCreator1())
print("\n")
print("App: Launched with the ConcreteCreator2.")
client_code(ConcreteCreator2())
4 应用示例
1、您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。
2、Hibernate 换数据库只需换方言和驱动就可以。
工厂方法将创建对象的工作让相应的工厂子类去实现,保证在新增工厂类时,不用修改原有代码,首先,创建一个抽象公共工厂类,并定义一个生产对象的方法,接着,创建抽象工厂类的 3 个子类,并重写方法,创建一个实例对象并返回
import abc
from factory.fruit import *
class AbstractFactory(object):
"""抽象工厂"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def get_fruit(self):
pass
class AppleFactory(AbstractFactory):
"""生产苹果"""
def get_fruit(self):
return Apple()
class BananaFactory(AbstractFactory):
"""生产香蕉"""
def get_fruit(self):
return Banana()
class OrangeFactory(AbstractFactory):
"""生产橘子"""
def get_fruit(self):
return Orange()
if __name__ == '__main__':
# 每个工厂负责生产自己的产品也避免了我们在新增产品时需要修改工厂的代码,而只要增加相应的工厂即可
instance_apple = AppleFactory().get_fruit()
instance_banana = BananaFactory().get_fruit()
instance_orange = OrangeFactory().get_fruit()
print(instance_apple)
print(instance_banana)
print(instance_orange)
5 适用场景
1. 当你在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。
工厂方法将创建产品的代码与实际使用产品的代码分离, 从而能在不影响其他代码的情况下扩展产品创建部分代码。
例如, 如果需要向应用中添加一种新产品, 你只需要开发新的创建者子类, 然后重写其工厂方法即可。
2. 如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。
继承可能是扩展软件库或框架默认行为的最简单方法。 但是当你使用子类替代标准组件时, 框架如何辨识出该子类?
解决方案是将各框架中构造组件的代码集中到单个工厂方法中, 并在继承该组件之外允许任何人对该方法进行重写。
让我们看看具体是如何实现的。 假设你使用开源 UI 框架编写自己的应用。 你希望在应用中使用圆形按钮, 但是原框架仅支持矩形按钮。 你可以使用 圆形按钮RoundButton子类来继承标准的 按钮Button类。 但是, 你需要告诉 UI框架UIFramework类使用新的子类按钮代替默认按钮。
为了实现这个功能, 你可以根据基础框架类开发子类 圆形按钮 UIUIWithRoundButtons , 并且重写其 createButton创建按钮方法。 基类中的该方法返回 按钮对象, 而你开发的子类返回 圆形按钮对象。 现在, 你就可以使用 圆形按钮 UI类代替 UI框架类。 就是这么简单!
3. 如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。
在处理大型资源密集型对象 (比如数据库连接、 文件系统和网络资源) 时, 你会经常碰到这种资源需求。
让我们思考复用现有对象的方法:
- 首先, 你需要创建存储空间来存放所有已经创建的对象。
- 当他人请求一个对象时, 程序将在对象池中搜索可用对象。
- …然后将其返回给客户端代码。
- 如果没有可用对象, 程序则创建一个新对象 (并将其添加到对象池中)。
这些代码可不少!而且它们必须位于同一处, 这样才能确保重复代码不会污染程序。
可能最显而易见, 也是最方便的方式, 就是将这些代码放置在我们试图重用的对象类的构造函数中。 但是从定义上来讲, 构造函数始终返回的是新对象, 其无法返回现有实例。
因此, 你需要有一个既能够创建新对象, 又可以重用现有对象的普通方法。 这听上去和工厂方法非常相像。
4. 举例:
- 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
- 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
- 3、设计一个连接服务器的框架,需要三个议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口。
6 实现方式
-
让所有产品都遵循同一接口。 该接口必须声明对所有产品都有意义的方法。
-
在创建类中添加一个空的工厂方法。 该方法的返回类型必须遵循通用的产品接口。
-
在创建者代码中找到对于产品构造函数的所有引用。 将它们依次替换为对于工厂方法的调用, 同时将创建产品的代码移入工厂方法。 你可能需要在工厂方法中添加临时参数来控制返回的产品类型。
-
工厂方法的代码看上去可能非常糟糕。 其中可能会有复杂的 switch分支运算符, 用于选择各种需要实例化的产品类。 但是不要担心,我们很快就会修复这个问题。
-
现在, 为工厂方法中的每种产品编写一个创建者子类, 然后在子类中重写工厂方法, 并将基本方法中的相关创建代码移动到工厂方法中。
-
如果应用中的产品类型太多, 那么为每个产品创建子类并无太大必要, 这时你也可以在子类中复用基类中的控制参数。
例如, 设想你有以下一些层次结构的类。 基类 邮件及其子类 航空邮件和 陆路邮件 ; 运输及其子类 飞机, 卡车和 火车 。 航空邮件仅使用 飞机对象, 而 陆路邮件则会同时使用 卡车和 火车对象。 你可以编写一个新的子类 (例如 火车邮件 ) 来处理这两种情况, 但是还有其他可选的方案。 客户端代码可以给 陆路邮件类传递一个参数, 用于控制其希望获得的产品。
- 如果代码经过上述移动后, 基础工厂方法中已经没有任何代码, 你可以将其转变为抽象类。 如果基础工厂方法中还有其他语句, 你可以将其设置为该方法的默认行为。
7 工厂方法模式优缺点
优点:
- 你可以避免创建者和具体产品之间的紧密耦合。
- 单一职责原则。 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。
- 开闭原则。 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。
1、一个调用者想创建一个对象,只要知道其名称就可以了。
2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:
- 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。
8 与其它模式的关系
-
在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。
-
抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。
-
你可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。
-
原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。
-
工厂方法是模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。