跳至主要內容

创建型设计模式

Mr.LRH大约 10 分钟

创建型设计模式

创建型模式提供创建对象的机制,增加已有代码的灵活性和可复用性。

工厂模式 (Factory Method)

在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

适合应用场景:

  • 如果无法预知对象确切类别及其依赖关系时。可以将创建产品的代码与实际使用产品的代码分离,从而能在不影响其他代码的情况下扩展产品创建部分代码。
  • 希望用户能扩展软件库或框架的内部组件时。在使用子类替代标准组件时,可以将各框架中构造组件的代码集中到单个工厂方法中,并在继承该组件之外允许任何人对该方法进行重写。
  • 希望复用现有对象来节省系统资源,而不是每次都重新创建对象。

优点:

  • 避免创建者和具体产品之间的紧密耦合。
  • 单一职责原则。可以将产品创建代码放在程序的单一位置,从而使得代码更容易维护。
  • 开闭原则。无需更改现有客户端代码,可以在程序中引入新的产品类型。

缺点:

  • 需要引入许多新的子类,代码可能会因此变得更复杂。最好的情况是将该模式引入创建者类的现有层次结构中。

Factory_Method_UML

代码示例
js
class ConcreteFactory1 {
  createProductA() {
    return new ConcreteProductA1()
  }

  createProductB() {
    return new ConcreteProductB1()
  }
}

class ConcreteFactory2 {
  createProductA() {
    return new ConcreteProductA2()
  }

  createProductB() {
    return new ConcreteProductB2()
  }
}

class ConcreteProductA1 {
  usefulFunctionA() {
    return 'The result of the product A1.'
  }
}

class ConcreteProductA2 {
  usefulFunctionA() {
    return 'The result of the product A2.'
  }
}

class ConcreteProductB1 {
  usefulFunctionB() {
    return 'The result of the product B1.'
  }

  anotherUsefulFunctionB(collaborator) {
    const result = collaborator.usefulFunctionA()
    return `The result of the B1 collaborating with the (${result})`
  }
}

class ConcreteProductB2 {
  usefulFunctionB() {
    return 'The result of the product B2.'
  }

  anotherUsefulFunctionB(collaborator) {
    const result = collaborator.usefulFunctionA()
    return `The result of the B2 collaborating with the (${result})`
  }
}

function clientCode(factory) {
  const productA = factory.createProductA()
  const productB = factory.createProductB()

  console.log(productB.usefulFunctionB())
  console.log(productB.anotherUsefulFunctionB(productA))
}

clientCode(new ConcreteFactory1())
clientCode(new ConcreteFactory2())

抽象工厂模式 (Abstract Factory)

创建一系列相关的对象,而无需指定其具体类。

适合应用场景:

  • 如果代码需要与多个不同系列的相关产品交互,但是由于无法提前获取相关信息,或者出于对未来扩展性的考虑,不希望代码基于产品的具体类进行构建。抽象工厂提供了一个接口,可用于创建每个系列产品的对象。只要代码通过该接口创建对象,那么就不会生成与应用程序已生成的产品类型不一致的产品。
  • 如果有一个基于一组抽象方法的类,且其主要功能因此变得不明确时。在设计良好的程序中,每个类仅负责一件事。如果一个类与多种类型产品交互,就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。

优点:

  • 确保同一工厂生成的产品相互匹配。
  • 避免客户端和具体产品代码的耦合。
  • 单一职责原则。可以将产品生成代码抽取到同一位置,使得代码易于维护。
  • 开闭原则。向应用程序中引入新产品变体时,无需修改客户端代码。

缺点:

  • 由于采用该模式需要向应用中引入众多接口和类,代码可能会比之前更加复杂。

Abstract_Factory_UML

代码示例
js
class ConcreteFactory1 {
  createProductA() {
    return new ConcreteProductA1()
  }
  createProductB() {
    return new ConcreteProductB1()
  }
}

class ConcreteFactory2 {
  createProductA() {
    return new ConcreteProductA2()
  }
  createProductB() {
    return new ConcreteProductB2()
  }
}

class ConcreteProductA1 {
  usefulFunctionA() {
    return 'The result of the product A1.'
  }
}

class ConcreteProductA2 {
  usefulFunctionA() {
    return 'The result of the product A2.'
  }
}

class ConcreteProductB1 {
  usefulFunctionB() {
    return 'The result of the product B1.'
  }
  anotherUsefulFunctionB(collaborator) {
    const result = collaborator.usefulFunctionA()
    return `The result of the B1 collaborating with the (${result})`
  }
}

class ConcreteProductB2 {
  usefulFunctionB() {
    return 'The result of the product B2.'
  }
  anotherUsefulFunctionB(collaborator) {
    const result = collaborator.usefulFunctionA()
    return `The result of the B2 collaborating with the (${result})`
  }
}

function clientCode(factory) {
  const productA = factory.createProductA()
  const productB = factory.createProductB()
  console.log(productB.usefulFunctionB())
  console.log(productB.anotherUsefulFunctionB(productA))
}

clientCode(new ConcreteFactory1())
clientCode(new ConcreteFactory2())

生成器模式 (Builder)

能够分步骤创建复杂对象。该模式允许你使用相同的创建代码生成不同类型和形式的对象。

适合应用场景:

  • 使用生成器模式可避免 “重叠构造函数(telescopic constructor)” 的出现。假设构造函数中有个十个可选参数,重载该构造函数,新建几个只有较少参数的简化版本,但这些构造函数仍需调用主构造函数,传递一些默认值来代替省略的参数。生成器模式可以分步骤生产对象,允许仅使用必须得步骤。应用该模式后,不需要将过多的参数传入构造函数。
  • 使用代码创建不同形式的产品时,可使用生成器模式。基本生成器接口定义了所有可能得制造步骤,具体生成器将实现这些步骤来制造特定形式的产品。同时,主管类将负责管理制造步骤的顺序。
  • 使用生成器构造组合树或其他复杂对象。生成器在执行制造步骤时,不能对外发布未完成的产品,可以避免客户端代码获取到不完整结果对象的情况。

优点:

  • 可以分步创建对象,暂缓创建步骤或递归运行创建步骤。
  • 生成不同形式的产品时,可以复用相同的制造代码。
  • 单一职责原则。可以将复杂构造代码从产品的业务逻辑中分离出来。

缺点:

  • 该模式需要新增多个类,代码整体复杂程度会有所增加。

Builder_UML

代码示例
js
class ConcreteBuilder1 {
  constructor() {
    this.reset()
  }
  reset() {
    this.product = new Product1()
  }
  producePartA() {
    this.product.parts.push('PartA1')
  }
  producePartB() {
    this.product.parts.push('PartB1')
  }
  producePartC() {
    this.product.parts.push('PartC1')
  }
  getProduct() {
    const result = this.product
    this.reset()
    return result
  }
}

class Product1 {
  constructor() {
    this.parts = []
  }
  listParts() {
    console.log(`Product parts: ${this.parts.join(', ')}\n`)
  }
}

class Director {
  setBuilder(builder) {
    this.builder = builder
  }
  buildMinimalViableProduct() {
    this.builder.producePartA()
  }
  buildFullFeaturedProduct() {
    this.builder.producePartA()
    this.builder.producePartB()
    this.builder.producePartC()
  }
}

function clientCode(director) {
  const builder = new ConcreteBuilder1()
  director.setBuilder(builder)

  console.log('Standard basic product:')
  director.buildMinimalViableProduct()
  builder.getProduct().listParts()

  console.log('Standard full featured product:')
  director.buildFullFeaturedProduct()
  builder.getProduct().listParts()

  console.log('Custom product:')
  builder.producePartA()
  builder.producePartC()
  builder.getProduct().listParts()
}
const director = new Director()
clientCode(director)

原型模式 (Prototype)

能够复制已有对象,而又无需使代码依赖它们所属的类。

适合应用场景:

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

优点:

  • 克隆对象,而无需与它们所属的具体类相耦合。
  • 克隆预生成原型,避免反复运行初始化代码。
  • 更方便地生成复杂对象。
  • 用继承以外的方式来处理复杂对象的不同配置。

缺点

  • 克隆包含循环引用的复杂对象可能会非常麻烦。

Prototype_UML

代码示例
js
class Prototype {
  clone() {
    const clone = Object.create(this)
    clone.component = Object.create(this.component)
    clone.circularReference = Object.assign(
      Object.assign({}, this.circularReference),
      { prototype: Object.assign({}, this) }
    )
    return clone
  }
}

class ComponentWithBackReference {
  constructor(prototype) {
    this.prototype = prototype
  }
}

function clientCode() {
  const p1 = new Prototype()
  p1.primitive = 245
  p1.component = new Date()
  p1.circularReference = new ComponentWithBackReference(p1)
  const p2 = p1.clone()
  if (p1.primitive === p2.primitive) {
    console.log('Primitive field values have been carried over to a clone. Yay!')
  } else {
    console.log('Primitive field values have not been copied. Booo!')
  }
  if (p1.component === p2.component) {
    console.log('Simple component has not been cloned. Booo!')
  } else {
    console.log('Simple component has been cloned. Yay!')
  }
  if (p1.circularReference === p2.circularReference) {
    console.log('Component with back reference has not been cloned. Booo!')
  } else {
    console.log('Component with back reference has been cloned. Yay!')
  }
  if (p1.circularReference.prototype === p2.circularReference.prototype) {
    console.log(
      'Component with back reference is linked to original object. Booo!'
    )
  } else {
    console.log('Component with back reference is linked to the clone. Yay!')
  }
}

clientCode()

单例模式 (Singleton)

能够保证一个类只有一个实例,并提供一个访问该实例的全局节点。

适合应用场景:

  • 如果程序中的某个类对于所有客户端只有一个可用的实例,可以使用单例模式。单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。该方法可以创建一个新对象,但如果该对象已经被创建,则返回已有的对象。
  • 需要更加严格地控制全局变量,可以使用单例模式。

优点:

  • 保证一个类只有一个实例。
  • 获得了一个指向该实例的全局访问节点。
  • 仅在首次请求单例对象时对其进行初始化。

缺点:

  • 违反了单一职责原则。
  • 单例模式可能掩盖不良设计,比如程序各组件之间相互了解过多等。
  • 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。
  • 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 于单例类的构造函 是私有的,而且绝大部分语言无法重写静态方法, 所以需要仔细考虑模拟单例的方法。要么干脆不编写测试代码, 或者不使用单例模式。

Singleton_UML

代码示例
js
class Singleton {
  constructor() {}

  static getInstance() {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton()
    }
    return Singleton.instance
  }

  someBusinessLogic() {
    // ...
  }
}

function clientCode() {
  const s1 = Singleton.getInstance()
  const s2 = Singleton.getInstance()

  if (s1 === s2) {
    console.log('Singleton works, both variables contain the same instance.')
  } else {
    console.log('Singleton failed, variables contain different instances.')
  }
}

clientCode()
上次编辑于:
贡献者: lrh21g