iOS设计模式之生成器模式

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

  • 本文主要介绍iOS设计模式中的生成器模式,生成器模式主要包含2个重要的角色:Director(指导者)和Builder(生成器)。

1. 什么是生成器

比如我们构建自己的房子的人会把工程外包给承包商,单一承包商不能建造整个房子,将其分解为几个部分,然后转包给几个实际的建筑商客户告诉承包商要什么样子的,然后承包商协调各房屋建筑商,决定需要什么。
有时,构建某些对象有多种不同的方式,如果这些逻辑包含在构建这些对象的类单一方法中,构建的逻辑会非常荒唐(例如,针对各种需求嵌套if-else或者switch- case语句)。如果能够把构建过程分解为客户-指导者-生成器的关系,那么过程将更容易管理与复用。
Builder知道究竟如何在缺少某些特定信息的来建造特定产品,Director知道Builder应该建造什么,但是并不意味着Director知道具体Builder究竟是什么。是一种整体-部分的关系

生成器模式:将一个复杂对象的构建与它的表现分离,使得同样的创建过程可以创建不同的表现

2. 何时使用生成器模式

  • 需要创建涉及各种部件的复杂对象。创建对象的算法应该独立与部件的装配方式,常见的例子是构建组合对象。
  • 构建过程需要以不同的方式(例如,部件或表现的不同组合)构建对象。

之前我们介绍了抽象工厂模式,它们有一些相似的地方,但是本质上来说是不同的。首先生成器关注的是分步创建复杂对象,很多时候同一同一类型的对象可以以不同的方式创建。其次,抽象工厂的重点在于创建简单或复杂产品的套件。生成器在多多步创建过程的最后一步返回产品,而抽象工厂则立即返回产品。
我们以一个APP为例,由很多组件组成,通过构建业务组件和基础组件等构造成一个app,而抽象工厂则是单一业务组件单独构造成一个app
其实最主要的是的构建步骤和构建方式我们可以看下它们的区别:

生成器 抽象工厂
构建复杂对象 构建简单或复杂对象
以多个步骤构建对象 以单一步骤构建对象
以多种方式构建对象 以单一方式构建对象
在构建过程的最后一步返回产品 立即返回产品
专注一个特定产品 强调一套产品
只有当产品较为复杂且需要详细配置时,使用生成器模式才有意义。
复制代码

3. 代码展示

我们看下示列

import XCTest

/// The Builder interface specifies methods for creating the different parts of
/// the Product objects.
protocol Builder {

    func producePartA()
    func producePartB()
    func producePartC()
}

/// The Concrete Builder classes follow the Builder interface and provide
/// specific implementations of the building steps. Your program may have
/// several variations of Builders, implemented differently.
class ConcreteBuilder1: Builder {

    /// A fresh builder instance should contain a blank product object, which is
    /// used in further assembly.
    private var product = Product1()

    func reset() {
        product = Product1()
    }

    /// All production steps work with the same product instance.
    func producePartA() {
        product.add(part: "PartA1")
    }

    func producePartB() {
        product.add(part: "PartB1")
    }

    func producePartC() {
        product.add(part: "PartC1")
    }

    /// Concrete Builders are supposed to provide their own methods for
    /// retrieving results. That's because various types of builders may create
    /// entirely different products that don't follow the same interface.
    /// Therefore, such methods cannot be declared in the base Builder interface
    /// (at least in a statically typed programming language).
    ///
    /// Usually, after returning the end result to the client, a builder
    /// instance is expected to be ready to start producing another product.
    /// That's why it's a usual practice to call the reset method at the end of
    /// the `getProduct` method body. However, this behavior is not mandatory,
    /// and you can make your builders wait for an explicit reset call from the
    /// client code before disposing of the previous result.
    func retrieveProduct() -> Product1 {
        let result = self.product
        reset()
        return result
    }
}

/// The Director is only responsible for executing the building steps in a
/// particular sequence. It is helpful when producing products according to a
/// specific order or configuration. Strictly speaking, the Director class is
/// optional, since the client can control builders directly.
class Director {

    private var builder: Builder?

    /// The Director works with any builder instance that the client code passes
    /// to it. This way, the client code may alter the final type of the newly
    /// assembled product.
    func update(builder: Builder) {
        self.builder = builder
    }

    /// The Director can construct several product variations using the same
    /// building steps.
    func buildMinimalViableProduct() {
        builder?.producePartA()
    }

    func buildFullFeaturedProduct() {
        builder?.producePartA()
        builder?.producePartB()
        builder?.producePartC()
    }
}

/// It makes sense to use the Builder pattern only when your products are quite
/// complex and require extensive configuration.
///
/// Unlike in other creational patterns, different concrete builders can produce
/// unrelated products. In other words, results of various builders may not
/// always follow the same interface.
class Product1 {

    private var parts = [String]()

    func add(part: String) {
        self.parts.append(part)
    }

    func listParts() -> String {
        return "Product parts: " + parts.joined(separator: ", ") + "\n"
    }
}

/// The client code creates a builder object, passes it to the director and then
/// initiates the construction process. The end result is retrieved from the
/// builder object.
class Client {
    // ...
    static func someClientCode(director: Director) {
        let builder = ConcreteBuilder1()
        director.update(builder: builder)
        
        print("Standard basic product:")
        director.buildMinimalViableProduct()
        print(builder.retrieveProduct().listParts())

        print("Standard full featured product:")
        director.buildFullFeaturedProduct()
        print(builder.retrieveProduct().listParts())

        // Remember, the Builder pattern can be used without a Director class.
        print("Custom product:")
        builder.producePartA()
        builder.producePartC()
        print(builder.retrieveProduct().listParts())
    }
    // ...
}

/// Let's see how it all comes together.
class BuilderConceptual: XCTestCase {

    func testBuilderConceptual() {
        let director = Director()
        Client.someClientCode(director: director)
    }
}
复制代码

打印结果

Standard basic product:
Product parts: PartA1

Standard full featured product:
Product parts: PartA1, PartB1, PartC1

Custom product:
Product parts: PartA1, PartC1
复制代码

4. 总结

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

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

猜你喜欢

转载自juejin.im/post/7104269609364095007