[Swift设计模式] builder

1,771 阅读3分钟
更多内容,欢迎关注公众号:Swift花园
喜欢文章?不如来点赞关注吧

实现 builder 模式,隐藏创建拥有大量属性的对象的复杂性。

 

builder 模式如何工作?

builder 模式可以采用多种方式实现,不过如果你明白这种模式的主要目标,那么用哪一种方式都无所谓。

Builder 设计模式的意图在于从复杂对象的表达中分离出构造对象的部分。

因此,如果你的对象有很多属性,而你想要隐藏构造过程的复杂性,那么你可以写一个 builder,通过它来构造你的对象。这个 builder 可以简单如一个 build 方法,也可以复杂如一个控制全部构建过程的外部类。这些都是由环境决定的。🏗

理论到此如此,让我们用 builder 模式来实战一番吧! 💪

 

简单发射器 builder

我认为 SKEmitterNode 是一个很不错的粒子。如果你打算创建自定义发射器并且通过程序设置属性,通过是在 SpriteKit 游戏当中。那么像下面这样的一个发射器 builder 类就是一种很合理的解决方案。👾

class EmitterBuilder {
    
    func build() -> SKEmitterNode {
        let emitter = SKEmitterNode()
        emitter.particleTexture = SKTexture(imageNamed: "MyTexture")
        emitter.particleBirthRate = 100
        emitter.particleLifetime = 60
        emitter.particlePositionRange = CGVector(dx: 100, dy: 100)
        emitter.particleSpeed = 10
        emitter.particleColor = .red
        emitter.particleColorBlendFactor = 1
        return emitter
    }
}

EmitterBuilder().build()

 

简单主题 builder

想象一下你正在为你的 UIKit 应用设计一个主题引擎,它有许多自定义字体、颜色等。构造标准主题时一个 builder 会很有用。 🔨

struct Theme {
    let textColor: UIColor?
    let backgroundColor: UIColor?
}

class ThemeBuilder {

    enum Style {
        case light
        case dark
    }

    func build(_ style: Style) -> Theme {
        switch style {
        case .light:
            return Theme(textColor: .black, backgroundColor: .white)
        case .dark:
            return Theme(textColor: .white, backgroundColor: .black)
        }
    }
}

let builder = ThemeBuilder()
let light = builder.build(.light)
let dark = builder.build(.dark)

 

"链式" URL builder

利用这种方式,你可以用各种方法配置对象,每一个方法都会返回同一个 builder 对象,然后在最后一步构建最终产品。⛓

class URLBuilder {
    
    private var components: URLComponents

    init() {
        self.components = URLComponents()
    }
    
    func set(scheme: String) -> URLBuilder {
        self.components.scheme = scheme
        return self
    }

    func set(host: String) -> URLBuilder {
        self.components.host = host
        return self
    }
    
    func set(port: Int) -> URLBuilder {
        self.components.port = port
        return self
    }

    func set(path: String) -> URLBuilder {
        var path = path
        if !path.hasPrefix("/") {
            path = "/" + path
        }
        self.components.path = path
        return self
    }

    func addQueryItem(name: String, value: String) -> URLBuilder  {
        if self.components.queryItems == nil {
            self.components.queryItems = []
        }
        self.components.queryItems?.append(URLQueryItem(name: name, value: value))
        return self
    }
    
    func build() -> URL? {
        return self.components.url
    }
}

let url = URLBuilder()
    .set(scheme: "https")
    .set(host: "localhost")
    .set(path: "api/v1")
    .addQueryItem(name: "sort", value: "name")
    .addQueryItem(name: "order", value: "asc")
    .build()

 

带 director 的 builder 模式

让我们来见一见director对象,它的功能是将 builder 和配置部分的代码解耦。举个例子,你本来想创建一个圆形,可是忽然改主意要换成一个正方形。这可以很容易实现。只需要创建一个新的 builder,其他的都不变。🎬

protocol NodeBuilder {
    var name: String { get set }
    var color: SKColor { get set }
    var size: CGFloat { get set }

    func build() -> SKShapeNode
}

protocol NodeDirector {
    var builder: NodeBuilder { get set }
    
    func build() -> SKShapeNode
}

class CircleNodeBuilder: NodeBuilder {
    var name: String = ""
    var color: SKColor = .clear
    var size: CGFloat = 0

    func build() -> SKShapeNode {
        let node = SKShapeNode(circleOfRadius: self.size)
        node.name = self.name
        node.fillColor = self.color
        return node
    }
}

class PlayerNodeDirector: NodeDirector {

    var builder: NodeBuilder
    
    init(builder: NodeBuilder) {
        self.builder = builder
    }
    
    func build() -> SKShapeNode {
        self.builder.name = "Hello"
        self.builder.size = 32
        self.builder.color = .red
        return self.builder.build()
    }
}

let builder = CircleNodeBuilder()
let director = PlayerNodeDirector(builder: builder)
let player = director.build()

 

基于block的builders

一种更 Swift 的方法可以使用 block 来代替 builder 类来配置对象。当然,可能有人要问这样还算不算 builder 模式... 😛

extension UILabel {

    static func build(block: ((UILabel) -> Void)) -> UILabel {
        let label = UILabel(frame: .zero)
        block(label)
        return label
    }
}

let label = UILabel.build { label in
    label.translatesAutoresizingMaskIntoConstraints = false
    label.text = "Hello wold!"
    label.font = UIFont.systemFont(ofSize: 12)
}

注意,builder 的实现根据特定的用例可能有所差别。有的时候 builder 会跟工厂结合。每个人对这个模式的解读都不同,但并不是问题。设计模式是制作精良的准则,但有的时候规则是可以被打破的。

 

我的公众号
这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~