Menu
CoddyTech

Pola Dekorator

Termasuk bagian Pemrograman Berorientasi Objek dalam perjalanan Swift di Coddy. Pelajaran 47 dari 57.

Pola decorator menambahkan perilaku pada sebuah object dengan membungkusnya dalam object lain yang memiliki protokol yang sama. Wrapper meneruskan pemanggilan ke object yang dibungkus dan menambahkan bagiannya sendiri sebelum atau sesudahnya:

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())

Output:

espresso + milk
250

Karena decorator itu sendiri adalah Coffee, decorator dapat membungkus satu sama lain dalam kombinasi dan urutan apa pun, tanpa perlu membuat subclass untuk setiap kombinasi:

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())

Output:

espresso + milk + milk + syrup
330

Decorator cocok untuk menambahkan hal lintas komponen seperti pencatatan log, caching, atau validasi di sekitar komponen yang sudah ada, tanpa mengubah komponen tersebut:

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))

Output:

fetching 7
row 7

Kode yang menggunakan object hanya melihat protokol, jadi tidak bisa membedakan object yang dihias dari object biasa. Itulah yang membuat pola ini aman untuk ditambahkan nanti.

func printReceipt(_ c: any Coffee) {      // berfungsi untuk Espresso dan semua versi yang dibungkus
    print("\(c.label()): \(c.cost())")
}
challenge icon

Tantangan

Mudah

Pizza dibuat menggunakan decorator. Protocol Pizza memiliki price() dan toppings() (array berisi nama). Base bernilai 800 tanpa topping. Implementasikan decorator Cheese (+150), Olives (+100), dan Double (menggandakan harga semua yang ada di dalamnya dan mengulang topping-nya dua kali). Lengkapi build(_:), yang memulai dari Base dan membungkusnya dengan satu decorator untuk setiap nama sesuai urutan.

Kode yang disediakan membaca baris berisi nama topping yang dipisahkan dengan koma (atau baris kosong), membuat setiap pizza, lalu mencetak 1050: cheese, olives atau 800: plain.

Tulis kodemu di Pizza.swift, Base.swift, Cheese.swift, Olives.swift, Double.swift, dan Functions.swift. main.swift berisi kode input/output yang disediakan dan tidak boleh diedit.

Coba sendiri

// Kode input/output yang disediakan: biarkan apa adanya
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 iconUji dirimu

Pelajaran ini berisi kuis singkat. Mulai pelajarannya untuk menjawab kuis dan melacak progresmu.

Semua pelajaran di Pemrograman Berorientasi Objek

Latihan sendiri: Playground Swift