الگوی طراحی Builder در سوئیفت

آفلاین
user-avatar
آرمان آبکار
16 مرداد 1400, خواندن در 3 دقیقه

در این مقاله یاد میگیریم چگونه الگوی طراحی سازنده (Builder) را در سوئیفت پیاده کنیم و پیچیدگی ایجاد object هایی که پراپرتی های زیادی دارند را پنهان کنیم.

الگوی طراحی Builder چگونه کار میکند؟

الگوی طراحی Builder به روش های زیادی قابل پیاده سازی است ولی این موضع اهمیت چندانی ندارد اگر هدف اصلی این الگو را بفهمید:

هدف الگوی طراحی Builder جداسازی روند ساخت یک object پیچیده از نمای خود آن است.

پس اگر object مورد نظر پراپرتی های زیادی دارد و میخواهید پیچیدگی نمونه سازی آن را پنهان کنید، میتوانید یک builder برای نمونه سازی این object بنویسید. این کار میتواند به سادگی ساختن متد یا یک کلاس خارجی باشد که کل روند نمونه سازی object را انجام می دهد. و البته این موضع به محیط بستگی دارد.

در زیر مثال هایی واقعی از الگوی طراحی 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 زنجیره ای:

با این روش می توانید object خود را با متد های مختلفی پیکر بندی کنید و هر کدام از آنها یک شی 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()

الگوی Builder با یک کارگردان

همان طور که از اسم آن مشخص است، این چیز کوچک 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()

Builder مبتنی بر بلوک

یک روش سوئیفتی تر این است که از بلوک ها به جای کلاس builder برای پیکر بندی object ها استفاده کنید. البته این رویکرد قابل بحث است که آیا این روش هنوز یک الگوی طراحی 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 با الگوی طراحی Factory ترکیب میشود. تقریبا هرکس این الگو را به گونه ای متفاوت تفسیر میکند ولی این مشکلی را ایجاد نمیکند. الگو های طراحی دستورالعمل های خوبی هستند ولی بعضی مواقع میتوانند بر اساس نیاز تغییر کنند.

منبع

چه امتیازی به این مقاله می دید؟
خیلی بد
بد
متوسط
خوب
عالی

دیدگاه‌ها و پرسش‌ها

برای ارسال دیدگاه لازم است، ابتدا وارد سایت شوید.

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید