โ† Kembali ke Design Pattern
๐ŸŽ€
Structural

Decorator

Membungkus sebuah objek untuk menambahkan perilaku baru secara dinamis, tanpa subclassing.

Masalahnya

Kamu perlu menambahkan tanggung jawab ke objek tertentu saja, bukan ke seluruh class. Subclassing menyebabkan ledakan kombinasi class saat fitur dicampur (misalnya teks bold + italic + underline).

Cara Kerja

  • โ€บSetiap decorator membungkus komponen dan mendelegasikan pemanggilan utamanya ke komponen tersebut
  • โ€บDecorator bisa ditumpuk dalam urutan apa pun, sangat mudah dikomposisikan
  • โ€บMengikuti Open/Closed Principle: perilaku bisa diperluas tanpa mengubah kode yang ada
  • โ€บLebih fleksibel dari inheritance karena diterapkan saat runtime per objek, bukan per class
  • โ€บHati-hati dengan nesting yang terlalu dalam, terlalu banyak lapisan bisa menyulitkan debugging

Kapan Digunakan

โœ“Menambahkan perilaku ke objek tertentu saat runtime tanpa memengaruhi objek lain
โœ“Saat subclassing akan menghasilkan ledakan class untuk setiap kombinasi
โœ“Saat perluasan lewat inheritance tidak memungkinkan (class sealed/final)
โœ“Wrapper untuk logging, validasi, caching, dan kompresi

Contoh di Dunia Nyata

๐ŸŽ
iOS

Combine operators (.map, .filter, .debounce chain), SwiftUI view modifiers

๐Ÿค–
Android

Compose Modifier chaining, Kotlin extension functions, OkHttp interceptors

๐ŸŒ
Web

Express middleware, React Higher-Order Components, Redux enhancers

Contoh Kode

Swift
protocol DataSource {
    func fetchData() -> String
}

class RemoteDataSource: DataSource {
    func fetchData() -> String { "raw data" }
}

class CachingDecorator: DataSource {
    private let wrapped: DataSource
    private var cache: String?
    init(_ ds: DataSource) { wrapped = ds }

    func fetchData() -> String {
        if let cached = cache { print("Cache hit"); return cached }
        let data = wrapped.fetchData()
        cache = data; return data
    }
}

class LoggingDecorator: DataSource {
    private let wrapped: DataSource
    init(_ ds: DataSource) { wrapped = ds }

    func fetchData() -> String {
        print("Fetching data...")
        let data = wrapped.fetchData()
        print("Got: \(data)")
        return data
    }
}

// Stack decorators โ€” order matters
let source: DataSource = LoggingDecorator(CachingDecorator(RemoteDataSource()))
_ = source.fetchData()  // "Fetching..." โ†’ cache miss โ†’ "Got: raw data"
_ = source.fetchData()  // "Fetching..." โ†’ Cache hit โ†’ "Got: raw data"