โ 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"