设计模式之创建型模式

创建型模式共有五种:单例模式、建造者模式、工厂方法模式、抽象工厂模式、原型模式。

  • 单例模式 :确保某一个类只有一个实例,并且提供一个全局访问点
  • 建造者模式 :用来创建复杂的复合对象
  • 工厂方法模式 :让子类来决定要创建哪个对象
  • 抽象工厂模式 :创建多个产品族中的产品对象
  • 原型模式 :通过复制原型来创建新对象

创建型模式就是用来创建对象的模式,抽象了实例化的过程,能够提升已有代码的灵活性和可复用性。所有的创建型模式都有两个共同点。

  1. 它们都将系统使用哪些具体类的信息封装起来;
  2. 它们隐藏了这些类的实例是如何被创建和组织的

单例模式

单例模式(亦称: 单件模式)指的是确保某一个类只有一个实例,并提供一个全局访问点。解决的是实体对象个数的问题,而其他的建造者模式都是解决new所带来的耦合关系问题。

所有单例的实现都包含以下两个相同的步骤:

  • 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
  • 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。

实现方法

  1. 在类中添加一个私有静态成员变量用于保存单例实例。
  2. 声明一个公有静态构建方法用于获取单例实例。
  3. 在静态方法中实现”延迟初始化”。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。
  4. 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。
  5. 检查客户端代码, 将对单例的构造函数的调用替换为对其静态构建方法的调用。

适用场景

  • 如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式
  • 如果你需要更加严格地控制全局变量, 可以使用单例模式。

建造者模式

建造者模式(生成器模式)指的是将一个产品的内部表示与产品的构造过程分割开来,从而可以使一个建造过程生成具体不同的内部表示的产品对象,即该模式允许你使用相同的创建代码生成不同类型和形式的对象。强调的是产品的构造过程。

实现方法

  1. 清晰地定义通用步骤, 确保它们可以制造所有形式的产品。 否则你将无法进一步实施该模式。

  2. 在基本生成器接口中声明这些步骤。

  3. 为每个形式的产品创建具体生成器类, 并实现其构造步骤。

    不要忘记实现获取构造结果对象的方法。 你不能在生成器接口中声明该方法, 因为不同生成器构造的产品可能没有公共接口, 因此你就不知道该方法返回的对象类型。 但是, 如果所有产品都位于单一类层次中, 你就可以安全地在基本接口中添加获取生成对象的方法。

  4. 考虑创建主管类。 它可以使用同一生成器对象来封装多种构造产品的方式。

  5. 客户端代码会同时创建生成器和主管对象。 构造开始前, 客户端必须将生成器对象传递给主管对象。 通常情况下, 客户端只需调用主管类构造函数一次即可。 主管类使用生成器对象完成后续所有制造任务。 还有另一种方式, 那就是客户端可以将生成器对象直接传递给主管类的制造方法。

  6. 只有在所有产品都遵循相同接口的情况下, 构造结果可以直接通过主管类获取。 否则, 客户端应当通过生成器获取构造结果。

适用场景

  • 使用生成器模式可避免 “重叠构造函数 (telescopic constructor)” 的出现。
  • 当你希望使用代码创建不同形式的产品 (例如石头木头房屋) 时, 可使用生成器模式。
  • 使用生成器构造组合树或其他复杂对象。

工厂方法模式

工厂方法模式(亦称: 虚拟构造函数)指的是在父类中提供一个创建对象的方法,由其子类决定要实例化的类,将实际创建工作推迟到子类中。它强调的是“单个对象”的变化。

实现方法

  1. 让所有产品都遵循同一接口。 该接口必须声明对所有产品都有意义的方法。

  2. 在创建类中添加一个空的工厂方法。 该方法的返回类型必须遵循通用的产品接口。

  3. 在创建者代码中找到对于产品构造函数的所有引用。 将它们依次替换为对于工厂方法的调用, 同时将创建产品的代码移入工厂方法。 你可能需要在工厂方法中添加临时参数来控制返回的产品类型。

    工厂方法的代码看上去可能非常糟糕。 其中可能会有复杂的 switch分支运算符, 用于选择各种需要实例化的产品类。 但是不要担心, 我们很快就会修复这个问题。

  4. 现在, 为工厂方法中的每种产品编写一个创建者子类, 然后在子类中重写工厂方法, 并将基本方法中的相关创建代码移动到工厂方法中。

  5. 如果应用中的产品类型太多, 那么为每个产品创建子类并无太大必要, 这时你也可以在子类中复用基类中的控制参数。

    例如, 设想你有以下一些层次结构的类。 基类 邮件及其子类 航空邮件陆路邮件运输及其子类 飞机, 卡车火车航空邮件仅使用 飞机对象, 而 陆路邮件则会同时使用 卡车火车对象。 你可以编写一个新的子类 (例如 火车邮件 ) 来处理这两种情况, 但是还有其他可选的方案。 客户端代码可以给 陆路邮件类传递一个参数, 用于控制其希望获得的产品。

  6. 如果代码经过上述移动后, 基础工厂方法中已经没有任何代码, 你可以将其转变为抽象类。 如果基础工厂方法中还有其他语句, 你可以将其设置为该方法的默认行为。

适用场景

  • 当你在编写代码的过程中 如果无法预知对象确切类别及其依赖关系时 可使用工厂方法。
  • 如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。
  • 如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。

抽象工厂模式

抽象工厂模式指的是提供一个创建一系列相关或相互依赖对象的接口,使得客户端可以在不必指定产品的具体类型的情况下,创建多个产品族中的产品对象,强调的是”系列对象“的变化。

实现方法

  1. 以不同的产品类型与产品变体为维度绘制矩阵。
  2. 为所有产品声明抽象产品接口。 然后让所有具体产品类实现这些接口。
  3. 声明抽象工厂接口, 并且在接口中为所有抽象产品提供一组构建方法。
  4. 为每种产品变体实现一个具体工厂类。
  5. 在应用程序中开发初始化代码。 该代码根据应用程序配置或当前环境, 对特定具体工厂类进行初始化。 然后将该工厂对象传递给所有需要创建产品的类。
  6. 找出代码中所有对产品构造函数的直接调用, 将其替换为对工厂对象中相应构建方法的调用。

适用场景

  • 如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂
  • 如果你有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。

原型工厂模式

原型模式(亦称: 克隆模式)指的是通过给出一个原型对象来指明所要创建的对象类型,然后用复制的方法来创建出更多的同类型对象。使你能够复制已有对象, 而又无需使代码依赖它们所属的类。

实现方法

  1. 创建原型接口, 并在其中声明 克隆方法。 如果你已有类层次结构, 则只需在其所有类中添加该方法即可。

  2. 原型类必须另行定义一个以该类对象为参数的构造函数。 构造函数必须复制参数对象中的所有成员变量值到新建实体中。 如果你需要修改子类, 则必须调用父类构造函数, 让父类复制其私有成员变量值。

    如果编程语言不支持方法重载, 那么你可能需要定义一个特殊方法来复制对象数据。 在构造函数中进行此类处理比较方便, 因为它在调用 new运算符后会马上返回结果对象。

  3. 克隆方法通常只有一行代码: 使用 new运算符调用原型版本的构造函数。 注意, 每个类都必须显式重写克隆方法并使用自身类名调用 new运算符。 否则, 克隆方法可能会生成父类的对象。

  4. 你还可以创建一个中心化原型注册表, 用于存储常用原型。

    你可以新建一个工厂类来实现注册表, 或者在原型基类中添加一个获取原型的静态方法。 该方法必须能够根据客户端代码设定的条件进行搜索。 搜索条件可以是简单的字符串, 或者是一组复杂的搜索参数。 找到合适的原型后, 注册表应对原型进行克隆, 并将复制生成的对象返回给客户端。

    最后还要将对子类构造函数的直接调用替换为对原型注册表工厂方法的调用。

适用场景

  • 如果你需要复制一些对象, 同时又希望代码独立于这些对象所属的具体类, 可以使用原型模式。
  • 如果子类的区别仅在于其对象的初始化方式, 那么你可以使用该模式来减少子类的数量。 别人创建这些子类的目的可能是为了创建特定类型的对象。

工厂方法,抽象工厂, 建造者都需要一个额外的工厂类来负责实例化“一个对象”,而原型模式则是通过原型(一个特殊的工厂类)来克隆“易变对象”。

设计模式系列:

  1. 设计模式之设计原则
  2. 设计模式之创建型模式
  3. 设计模式之结构型模式
  4. 设计模式之行为模式

参考 - 深入设计模式