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

نمط المُزيّن

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

يضيف نمط decorator سلوكًا إلى كائن من خلال تغليفه بكائن آخر يستخدم البروتوكول نفسه. يمرّر الغلاف الاستدعاء إلى الكائن المغلَّف ويضيف جزأه الخاص قبل ذلك أو بعده:

protocol Coffee {
    func cost() -> Int
    func label() -> String
}

struct Espresso: Coffee {
    func cost() -> Int { 200 }
    func label() -> String { "espresso" }
}

struct WithMilk: Coffee {
    let base: any Coffee
    func cost() -> Int { base.cost() + 50 }
    func label() -> String { base.label() + " + milk" }
}

let drink = WithMilk(base: Espresso())
print(drink.label())
print(drink.cost())

الناتج:

espresso + milk
250

بما أن المُزخرف هو نفسه Coffee، يمكن للمُزخرفات تغليف بعضها بأي تركيبة وترتيب، من دون الحاجة إلى فئة فرعية لكل تركيبة:

protocol Coffee { func cost() -> Int; func label() -> String }
struct Espresso: Coffee { func cost() -> Int { 200 }; func label() -> String { "espresso" } }
struct WithMilk: Coffee {
    let base: any Coffee
    func cost() -> Int { base.cost() + 50 }
    func label() -> String { base.label() + " + milk" }
}
struct WithSyrup: Coffee {
    let base: any Coffee
    func cost() -> Int { base.cost() + 30 }
    func label() -> String { base.label() + " + syrup" }
}

let fancy = WithSyrup(base: WithMilk(base: WithMilk(base: Espresso())))
print(fancy.label())
print(fancy.cost())

الناتج:

espresso + milk + milk + syrup
330

تُلائم المُزيّنات الإضافات الشاملة مثل التسجيل المؤقت أو التخزين المؤقت أو التحقّق حول مكوّن موجود، مع بقائه دون تغيير:

protocol DataSource { func fetch(_ id: Int) -> String }
struct Database: DataSource { func fetch(_ id: Int) -> String { "row \(id)" } }

struct Logged: DataSource {
    let inner: any DataSource
    func fetch(_ id: Int) -> String {
        print("fetching \(id)")
        return inner.fetch(id)
    }
}

let source: any DataSource = Logged(inner: Database())
print(source.fetch(7))

الناتج:

fetching 7
row 7

الكود الذي يستخدم الكائن لا يرى سوى البروتوكول، لذلك لا يمكنه التمييز بين كائن مُزيَّن وكائن عادي. وهذا ما يجعل إضافة هذا النمط لاحقًا آمنة.

func printReceipt(_ c: any Coffee) {      // يعمل مع Espresso وأي نسخة ملفوفة
    print("\(c.label()): \(c.cost())")
}
challenge icon

التحدي

سهل

تُبنى البيتزا باستخدام المُزخرفات. يحتوي البروتوكول Pizza على price() وtoppings() (مصفوفة من الأسماء). تكون Base بسعر 800 ومن دون إضافات. نفّذ المُزخرفات Cheese (+150) وOlives (+100) وDouble (يضاعف سعر كل ما بداخله ويكرر إضافاته مرتين). أكمل build(_:)، الذي يبدأ من Base ويغلّفه بمُزخرف واحد لكل اسم بالترتيب.

يقرأ الكود المُ provided أسطرًا تحتوي على أسماء إضافات مفصولة بفواصل (أو سطرًا فارغًا)، ويبني كل بيتزا ويطبع 1050: cheese, olives أو 800: plain.

يجب أن تضع كودك في Pizza.swift وBase.swift وCheese.swift وOlives.swift وDouble.swift وFunctions.swift. يحتوي main.swift على كود الإدخال/الإخراج المُ provided ولا يمكن تعديله.

جرّب بنفسك

// كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
var input: [String] = []
while let line = readLine() { input.append(line) }
for line in input {
    let names = line.isEmpty ? [] : line.split(separator: ",").map(String.init)
    let p = build(names)
    let tops = p.toppings()
    print("\(p.price()): " + (tops.isEmpty ? "plain" : tops.joined(separator: ", ")))
}
quiz iconاختبر نفسك

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

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

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