Menu
Coddy logo textTech

תבנית העיצוב

חלק מהיחידה תכנות מונחה עצמים במסלול ה-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 ועוטפת אותה בעוטף אחד לכל שם, לפי הסדר.

הקוד שסופק קורא שורות של שמות תוספות המופרדים בפסיקים (או שורה ריקה), בונה כל פיצה ומדפיס 1050: cheese, olives או 800: plain.

הקוד שלכם ייכתב בקבצים Pizza.swift, Base.swift, Cheese.swift, Olives.swift, Double.swift ו־Functions.swift. הקובץ main.swift מכיל את קוד הקלט/פלט שסופק, ואי אפשר לערוך אותו.

נסו בעצמכם

// קוד הקלט/פלט שסופק: יש להשאיר אותו כפי שהוא
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בחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: Swift אונליין