โ Kembali ke Konsep OOP
๐
Prinsip
Liskov Substitution
Subtype harus bisa menggantikan base type-nya tanpa mengubah kebenaran program.
Masalahnya
Jika subclass meng-override perilaku dengan cara yang melanggar ekspektasi base class-nya, misalnya melempar error untuk operasi yang didukung base class atau mengembalikan hasil yang tak terduga, kode yang mengandalkan base type akan rusak saat diberi subtype tersebut.
Cara Kerja
- โบSubclass harus menghormati kontrak (precondition, postcondition, invariant) dari superclass-nya
- โบMethod yang di-override harus menerima input setidaknya seluas method dasarnya dan mengembalikan output yang tidak lebih longgar
- โบContoh pelanggaran klasik: Square extends Rectangle tetapi merusak independensi setWidth/setHeight
- โบJika subtype tidak bisa sepenuhnya memenuhi kontrak base type, pertimbangkan ulang relasi inheritance-nya
Kapan Digunakan
โMerancang hierarki class di mana subclass harus bisa langsung menggantikan induknya
โMeninjau inheritance untuk mencari perilaku override yang mengejutkan
โMemilih antara inheritance dan composition untuk relasi yang "hampir is-a"
Contoh di Dunia Nyata
๐
iOS
Subclass UIView mana pun harus bekerja dengan benar di mana pun UIView diharapkan
๐ค
Android
Subclass RecyclerView.ViewHolder mana pun harus berperilaku benar di dalam adapter
๐
Web
Implementasi Storage mana pun (localStorage/sessionStorage) harus mendukung kontrak get/set/remove yang sama
Contoh Kode
Swift
// โ Violation: Penguin can't fly, breaking the Bird contract
// class Penguin: Bird { override func fly() { fatalError("can't fly") } }
// โ
Fix: separate flying ability into its own protocol
protocol Bird {
func eat()
}
protocol FlyingBird: Bird {
func fly()
}
struct Sparrow: FlyingBird {
func eat() { print("Sparrow eating") }
func fly() { print("Sparrow flying") }
}
struct Penguin: Bird {
func eat() { print("Penguin eating") }
// no fly() โ Penguin is not a FlyingBird, and that's fine
}
func feed(_ bird: Bird) { bird.eat() }
feed(Penguin()) // works correctly for any Bird