Respectă promisiunea
Principiul substituției Liskov spune: oriunde codul așteaptă o interfață, fiecare implementare a acelei interfețe trebuie să funcționeze corect. O implementare care se potrivește tehnic, dar se comportă greșit — declanșează panic, nu face nimic, minte — strică orice funcție care s-a încrezut în interfață.
O interfață este o promisiune. Fly() promite zborul. Un tip care implementează Fly() prăbușind programul a semnat un contract pe care nu îl poate respecta.
Un substitut care încalcă înțelegerea
Interfața Bird promite Fly(). Vrăbiile se țin de cuvânt. Pinguinii sunt și ei păsări — așa că pare firesc să implementezi interfața... cu un panic, pentru că pinguinii nu pot zbura.
Acum airShow, care funcționează pentru orice pasăre cinstită, se prăbușește la un pinguin. Problema nu este în airShow — este în substitutul care a încălcat promisiunea.
Pinguinul care prăbușește spectacolul
airShow se încrede în interfață și apelează Fly() pentru fiecare pasăre. Vrabia își face numărul; pinguinul declanșează panic și doboară tot programul (recover doar îndulcește aterizarea, ca să putem afișa mesajul).
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
type Bird interface {
Fly() string
}
type Sparrow struct{}
func (s Sparrow) Fly() string {
return "vrabie: zboară jos"
}
// Penguin semnează contractul — dar nu îl poate respecta.
type Penguin struct{}
func (p Penguin) Fly() string {
panic("pinguinii nu pot zbura")
}
func airShow(birds []Bird) {
for _, b := range birds {
fmt.Println(b.Fly())
}
}
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("spectacolul s-a prăbușit:", r)
}
}()
airShow([]Bird{Sparrow{}, Penguin{}})
}
Separă promisiunile
Soluția: promite doar ceea ce este adevărat. Împarte promisiunea prea largă în Flyer și Swimmer. Vrabia implementează Flyer, pinguinul implementează Swimmer — și niciun tip nu semnează un contract pe care nu îl poate respecta.
Partea cea mai bună este cine prinde acum greșeala: transmiterea unui pinguin către airShow nu mai este o prăbușire la execuție — este o eroare de compilare. Programul nici măcar nu se construiește.
Fiecare tip își respectă promisiunea
airShow acceptă Flyer, poolShow acceptă Swimmer. Fiecare animal apare doar acolo unde promisiunea lui este adevărată — substituția este sigură prin însăși construcția codului.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
type Flyer interface {
Fly() string
}
type Swimmer interface {
Swim() string
}
type Sparrow struct{}
func (s Sparrow) Fly() string {
return "vrabie: zboară jos"
}
type Penguin struct{}
func (p Penguin) Swim() string {
return "pinguin: se scufundă adânc"
}
func airShow(flyers []Flyer) {
for _, f := range flyers {
fmt.Println(f.Fly())
}
}
func poolShow(swimmers []Swimmer) {
for _, s := range swimmers {
fmt.Println(s.Swim())
}
}
func main() {
airShow([]Flyer{Sparrow{}})
poolShow([]Swimmer{Penguin{}})
// airShow([]Flyer{Penguin{}}) nici măcar nu s-ar compila.
}
Orice Flyer se poate alătura
Răsplata interfețelor cinstite: un nou Eagle se alătură spectacolului aerian fără niciun risc — orice implementează Flyer chiar zboară, așa că airShow nu trebuie să se îndoiască niciodată de datele primite.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
type Flyer interface {
Fly() string
}
type Sparrow struct{}
func (s Sparrow) Fly() string {
return "vrabie: zboară jos"
}
// Eagle este nou — și chiar zboară, deci se substituie în siguranță.
type Eagle struct{}
func (e Eagle) Fly() string {
return "vultur: se înalță sus"
}
func airShow(flyers []Flyer) {
for _, f := range flyers {
fmt.Println(f.Fly())
}
}
func main() {
airShow([]Flyer{Sparrow{}, Eagle{}})
}