持续创作,加速成长!这是我参与「掘金日新计划 · 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. 总结
客户端代码会同时创建生成器
和主管对象
。 构造开始前, 客户端必须将生成器对象传递给主管对象。 通常情况下, 客户端只需调用主管类构造函数一次
即可。 主管类使用生成器对象完成后续所有制造任务。 还有另一种方式, 那就是客户端可以将生成器对象直接传递给主管类的制造方法。生成器模式主要优点:
- 你可以
分步创建
对象, 暂缓创建步骤或递归运行创建步骤。 - 生成不同形式的产品时, 你可以
复用相同的
制造代码。 单一职责
原则。 你可以将复杂构造代码从产品的业务逻辑中分离出来。