Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط البناء

جزء من قسم البرمجة كائنية التوجه في رحلة Swift على Coddy. الدرس 48 من 57.

يجمع نمط الباني كائنًا معقدًا خطوةً بخطوة. تضبط كل خطوة جزءًا واحدًا وتعيد الباني، بحيث يمكن تسلسل الخطوات، ثم ينشئ build() النهائي الكائن المكتمل:

struct Request {
    let url: String
    let method: String
    let headers: [String: String]
}

struct RequestBuilder {
    private var url = ""
    private var method = "GET"
    private var headers: [String: String] = [:]

    func url(_ u: String) -> RequestBuilder { var b = self; b.url = u; return b }
    func method(_ m: String) -> RequestBuilder { var b = self; b.method = m; return b }
    func header(_ k: String, _ v: String) -> RequestBuilder { var b = self; b.headers[k] = v; return b }
    func build() -> Request { Request(url: url, method: method, headers: headers) }
}

let req = RequestBuilder().url("/users").method("POST").header("Auth", "token").build()
print("\(req.method) \(req.url) \(req.headers.count) header")

الناتج:

POST /users 1 header

يمكن للمنشئ التحقق من الإعداد بالكامل قبل إنشاء الكائن، لذا لا يتم إنشاء كائن غير صالح أبدًا. هنا ترمي build() خطأً عند فقدان جزء مطلوب:

enum BuildError: Error { case missingURL }

struct Request { let url: String; let method: String }

struct RequestBuilder {
    private var url: String?
    private var method = "GET"
    func url(_ u: String) -> RequestBuilder { var b = self; b.url = u; return b }
    func method(_ m: String) -> RequestBuilder { var b = self; b.method = m; return b }
    func build() throws -> Request {
        guard let url else { throw BuildError.missingURL }
        return Request(url: url, method: method)
    }
}

do {
    _ = try RequestBuilder().method("PUT").build()
} catch {
    print("cannot build: \(error)")
}

الناتج:

cannot build: missingURL

تنسخ كل خطوة البنية وتغيّر النسخة، لذا يمكن إعادة استخدام منشئ تم تكوينه جزئيًا كقالب لعدة كائنات:

struct PizzaBuilder {
    private var size = "medium"
    private var toppings: [String] = []
    func size(_ s: String) -> PizzaBuilder { var b = self; b.size = s; return b }
    func add(_ t: String) -> PizzaBuilder { var b = self; b.toppings.append(t); return b }
    func build() -> String { "\(size) pizza with " + (toppings.isEmpty ? "nothing" : toppings.joined(separator: ", ")) }
}

let base = PizzaBuilder().size("large").add("cheese")
print(base.add("ham").build())
print(base.add("olives").build())

الناتج:

large pizza with cheese, ham
large pizza with cheese, olives

تغطي القيم الافتراضية للمعاملات في Swift الحالات البسيطة بالفعل (Request(url: "/", method: "GET")). يصبح نمط البناء مفيدًا عندما يحتوي كائن على أجزاء اختيارية كثيرة، أو أجزاء تعتمد على بعضها، أو قواعد يجب التحقق منها قبل أن يكون موجودًا.

// بسيط: الوسائط الافتراضية كافية
struct Point { var x = 0, y = 0 }
// معقد: أجزاء اختيارية كثيرة والتحقق -> builder
let report = ReportBuilder().title("Q3").add(section: s1).add(section: s2).footer("draft").build()
challenge icon

التحدي

سهل

اكتب منشئًا لرسائل البريد الإلكتروني. يحتوي EmailBuilder على خطوات قابلة للتسلسل هي to(_:) (يمكن استدعاؤها عدة مرات لإضافة مستلمين)، وsubject(_:)، وbody(_:)، وurgent(). تُرجع build() قيمة من النوع Email أو ترمي الخطأ EmailError.noRecipients أو EmailError.noSubject. يضيف البريد الإلكتروني العاجل [URGENT] أمام موضوعه.

يقرأ الكود المقدم الأوامر لكل رسالة بريد إلكتروني، بحيث تفصل بينها أسطر --- (مثل to ada@x.io وsubject Hello وbody text وurgent ثم send)، ويطبع To: ada@x.io, bo@x.io | [URGENT] Hello | text أو error: no recipients أو error: no subject.

ضع الكود الخاص بك في Email.swift وEmailError.swift وEmailBuilder.swift. يحتوي main.swift على كود الإدخال/الإخراج المقدم، ولا يمكن تعديله.

جرّب بنفسك

// كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
var input: [String] = []
while let line = readLine() { input.append(line) }
var builder = EmailBuilder()
for line in input {
    let p = line.split(separator: " ", maxSplits: 1).map(String.init)
    switch p[0] {
    case "to": builder = builder.to(p[1])
    case "subject": builder = builder.subject(p[1])
    case "body": builder = builder.body(p[1])
    case "urgent": builder = builder.urgent()
    case "send":
        do {
            let e = try builder.build()
            print("To: \(e.recipients.joined(separator: ", ")) | \(e.subject) | \(e.body)")
        } catch EmailError.noRecipients {
            print("error: no recipients")
        } catch {
            print("error: no subject")
        }
    default: builder = EmailBuilder()
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: Playground لـ Swift