Skip to content

对象创建模式

概念

定义

  • 通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。

要点

  • 它是接口抽象之后的第一步工作。
  • 对象创建的不稳定,容易诱发生命周期后续阶段的混乱。

工厂方法模式(FactoryMethod)

动机

  • 在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。
  • 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?

模式定义

  • 定义一个用于创建对象的接口,让子类决定实例化哪一个类。
  • Factory Method 使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。

UML 类图

x

角色

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

代码

// 抽象产品:提供了产品的接口
interface Product {
  show(): void;
}

// 具体产品1:实现抽象产品中的抽象方法
class ConcreteProduct1 implements Product {
  public show(): void {
    console.log('具体产品1显示...');
  }
}

// 具体产品2:实现抽象产品中的抽象方法
class ConcreteProduct2 implements Product {
  public show(): void {
    console.log('具体产品2显示...');
  }
}

// 抽象工厂:提供了厂品的生成方法
interface AbstractFactory {
  newProduct(): Product;
}

// 具体工厂1:实现了厂品的生成方法
class ConcreteFactory1 implements AbstractFactory {
  public newProduct(): Product {
    console.log('具体工厂1--生产-->具体产品1');
    return new ConcreteProduct1();
  }
}

// 具体工厂2:实现了厂品的生成方法
class ConcreteFactory2 implements AbstractFactory {
  public newProduct(): Product {
    console.log('具体工厂2--生产-->具体产品2');
    return new ConcreteProduct2();
  }
}

export default class AbstractFactoryTest {
  public static main(args: string[]): void {
    const af: AbstractFactory = new ConcreteFactory2();
    const p: Product = af.newProduct();
    p.show();
  }
}

要点总结

  • Factory Method 模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合的关系(new)会导致软件的脆弱。
  • Factory Method 模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好的解决了这种紧耦合的关系。
  • Factory Method模式解决了“单个对象”的需求变化,缺点在于要求创建方法/参数相同。

抽象工厂模式(AbstractFactory)

动机

  • 在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
  • 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?

模式定义

  • 提供一个接口,让该接口负责创建一系列 “相关或者相互依赖的对象”,无需指定它们具体的类
  • 何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。

问题域

  • 横轴:产品等级,同一级别产品; 纵轴:产品族,同一个工厂。
  • 图示

x

UML 类图

x

角色

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
  • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。

代码

// 抽象产品
interface Product1 {
  show(): void;
}
interface Product2 {
  show(): void;
}

// 具体产品
class ConcreteProduct1 implements Product1 {
  public show(): void {
    console.log('具体产品 1 展示...');
  }
}
class ConcreteProduct2 implements Product2 {
  public show(): void {
    console.log('具体产品 2 展示...');
  }
}

interface AbstractFactory {
  newProduct1(): Product1;
  newProduct2(): Product2;
}

class ConcreteFactory1 implements AbstractFactory {
  public newProduct1(): Product1 {
    console.log('具体工厂 1 生成-->具体产品 1...');
    return new ConcreteProduct1();
  }

  public newProduct2(): Product2 {
    console.log('具体工厂 1 生成-->具体产品 2...');
    return new ConcreteProduct2();
  }
}

export default class AbstractFactoryTest {
  public static main(args: string[]): void {
    const af: AbstractFactory = new ConcreteFactory1();
    const p1: Product1 = af.newProduct1();
    const p2: Product2 = af.newProduct2();
    p1.show();
    p2.show();
  }
}

要点总结

  • 如果没有应对“多系列对象构建”的需求变化,则没有必要使用Abstract Factory模式,这时候使用简单的工厂完全可以。
  • “系列对象”指的是在某一特定系列下的对象之间有相互依赖、或作用的关系。不同系列的对象之间不能相互依赖。
  • Abstract Factory 模式主要在于应对“新系列”的需求变动。其缺点在于难以应对“新对象”的需求变动。

原型模式(Prototype)

动机

  • 在软件系统中,经常面临着“某些结构复杂的对象”的创建工作; 由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。
  • 如何应对这种变化?如何向“客户程序(使用这些对象的程序)”隔离出“这些易变对象” ,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?

模式定义

  • 使用原型实例指定创建对象的种类,然后通过拷贝原型来创建新的对象。
  • 用于创建重复的对象,同时又能保证性能。

UML 类图

x

角色

  • 抽象原型类:规定了具体原型对象必须实现的接口。
  • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

代码

interface Cloneable {
  clone(): Cloneable;
}

class Prototype implements Cloneable {
  clone(): Cloneable {
    // function F() {}
    // F.prototype = this;
    // return new F();
    return Object.create(this);
  }
}

// 具体原型类
class Realizetype extends Prototype implements Cloneable {
  constructor() {
    super();
    console.log('具体原型创建成功!');
  }

  public clone(): Cloneable {
    console.log('具体原型复制成功!');
    return super.clone();
  }
}

export default class PrototypeTest {
  public static main(args: string[]): void {
    const obj1: Realizetype = new Realizetype();
    const obj2: Realizetype = obj1.clone();
    console.log('obj1 === obj2 ?', obj1 === obj2);
  }
}

要点总结

  • Prototype 模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些“易变类”拥有“稳定的接口”。
  • Prototype 模式对于“如何创建易变类的实体对象”采用“原型克隆”的方法来做,它使得我们可以非常灵活地动态创建“拥有某些稳定接口”的新对象——所需工作仅仅是注册一个新类的对象(即原型),然后在任何需要的地方Clone。
  • Prototype模式中的Clone方法可以利用某些框架中的序列化来实现深拷贝。
  • 适用于大对象重复创建(初始化)的场景,或者初始化过程复杂、步骤比较多的场景。 使用原型模式后,可以多次拷贝“原型”作为对象的构造过程。这样可以节省系统开销,还可以减少程序的重复代码。

建造者模式(Builder)

动机

  • 在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成; 由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
  • 如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?

模式定义

  • 将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)。
  • 定义

    • 建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。
    • 将一个复杂对象的 构建表示 分离,使得同样的构建过程可以创建不同的表示。
    • 分离了部件的构造(由 Builder 来负责)和装配(由 Director 负责)。
    • 不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。

UML 类图

x

角色

  • 抽象建造者类( Builder ):规定实现复杂对象的那些部分的创建,并不涉及具体的对象的创建。
  • 具体建造者类 ( ConcreteBuilder ):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
  • 产品类( Product ):要创建的复杂对象。
  • 指挥者类( Director ):调用具体建造者来创建复杂对象的各个部分,不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

代码

// 产品角色 Product:包含多个组成部件的复杂对象。
class Product {
  private partA: string;
  private partB: string;
  private partC: string;
  public setPartA(partA: string): void {
    this.partA = partA;
  }
  public setPartB(partB: string): void {
    this.partB = partB;
  }
  public setPartC(partC: string): void {
    this.partC = partC;
  }
  public show(): void {
    // 显示产品的特性
    console.log(this);
  }
}

// 抽象建造者 Builder:包含创建产品各个子部件的抽象方法。
abstract class Builder {
  // 创建产品对象
  protected product: Product = new Product();
  public abstract buildPartA(): void;
  public abstract buildPartB(): void;
  public abstract buildPartC(): void;
  // 返回产品对象
  public getResult(): Product {
    return this.product;
  }
}

// 具体建造者 ConcreteBuilder:实现了抽象建造者接口。
class ConcreteBuilder extends Builder {
  public buildPartA(): void {
    this.product.setPartA('建造 PartA');
  }
  public buildPartB(): void {
    this.product.setPartB('建造 PartB');
  }
  public buildPartC(): void {
    this.product.setPartC('建造 PartC');
  }
}

// 指挥者 Director:调用建造者中的方法完成复杂对象的创建
class Director {
  private builder: Builder;
  public constructor(builder: Builder) {
    this.builder = builder;
  }
  // 产品构建与组装方法
  public construct(): Product {
    this.builder.buildPartA();
    this.builder.buildPartB();
    this.builder.buildPartC();
    return this.builder.getResult();
  }
}

// 客户类
export default class BuilderPatternClient {
  public static main(args: string[]): void {
    const builder: Builder = new ConcreteBuilder();
    const director: Director = new Director(builder);
    const product: Product = director.construct();
    product.show();
  }
}

模式对比

  • 工厂方法模式 vs 建造者模式

    • 工厂方法模式注重的是整体对象的创建方式;
    • 而建造者模式 注重的是部件构建的过程,意在通过一步一步地确构造创建出一个复杂的对象。
  • 抽象工厂模式 vs 建造者模式

    • 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是 不需要关心构建过程,只关心什么产品由什么工厂生产即可
    • 建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

要点总结

  • Builder 模式主要用于“分步骤构建一个复杂的对象”。在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。
  • 变化点在哪里,封装哪里—— Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动。