در این مقاله یاد میگیریم چگونه الگوی طراحی سازنده (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 ترکیب میشود. تقریبا هرکس این الگو را به گونه ای متفاوت تفسیر میکند ولی این مشکلی را ایجاد نمیکند. الگو های طراحی دستورالعمل های خوبی هستند ولی بعضی مواقع میتوانند بر اساس نیاز تغییر کنند.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید