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 — aruncă erori, nu face nimic, minte — strică orice funcție care s-a încrezut în interfață.
O interfață este o promisiune. fly() promite zborul. O clasă care implementează fly() prăbușindu-se a semnat un contract pe care nu îl poate respecta.
Un înlocuitor 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... aruncând o eroare, pentru că pinguinii nu pot zbura.
Acum airShow, care funcționează pentru orice pasăre onestă, se prăbușește pe un pinguin. Problema nu este în airShow — este în înlocuitorul care a încălcat promisiunea.
Pinguinul care strică spectacolul
airShow se încrede în interfață și apelează fly() pe fiecare pasăre. Vrabia își face numărul; pinguinul aruncă o eroare și oprește spectacolul chiar acolo (blocul try/catch doar îndulcește aterizarea, ca să putem afișa mesajul).
Ieșire: program TypeScript și rezultatul capturat
interface Bird {
fly(): string
}
class Sparrow implements Bird {
fly(): string {
return "vrabie: zboară jos"
}
}
// Penguin semnează contractul — dar nu îl poate respecta.
class Penguin implements Bird {
fly(): string {
throw new Error("pinguinii nu pot zbura")
}
}
function airShow(birds: Bird[]): void {
for (const b of birds) {
console.log(b.fly())
}
}
try {
airShow([new Sparrow(), new Penguin()])
} catch (err) {
console.log("spectacolul s-a prăbușit:", (err as Error).message)
}
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 nicio clasă nu semnează un contract pe care nu îl poate respecta.
Partea cea mai bună este cine prinde acum greșeala: trimiterea unui pinguin către airShow nu mai este o prăbușire în timpul rulării programului — este o eroare de tip, raportată înainte ca programul să pornească.
Fiecare tip își respectă promisiunea
airShow acceptă Flyer, poolShow acceptă Swimmer. Fiecare animal apare doar acolo unde promisiunea lui este valabilă — substituția este sigură prin construcție.
Ieșire: program TypeScript și rezultatul capturat
interface Flyer {
fly(): string
}
interface Swimmer {
swim(): string
}
class Sparrow implements Flyer {
fly(): string {
return "vrabie: zboară jos"
}
}
class Penguin implements Swimmer {
swim(): string {
return "pinguin: se scufundă adânc"
}
}
function airShow(flyers: Flyer[]): void {
for (const f of flyers) {
console.log(f.fly())
}
}
function poolShow(swimmers: Swimmer[]): void {
for (const s of swimmers) {
console.log(s.swim())
}
}
airShow([new Sparrow()])
poolShow([new Penguin()])
// airShow([new Penguin()]) nici măcar nu ar trece verificarea de tipuri.
Orice Flyer se poate alătura
Răsplata interfețelor oneste: o nouă clasă Eagle se alătură spectacolului aerian fără niciun risc — orice implementează Flyer chiar zboară, așa că airShow nu are nevoie să se îndoiască de datele primite.
Ieșire: program TypeScript și rezultatul capturat
interface Flyer {
fly(): string
}
class Sparrow implements Flyer {
fly(): string {
return "vrabie: zboară jos"
}
}
// Eagle este nou — și chiar zboară, deci poate fi substituit în siguranță.
class Eagle implements Flyer {
fly(): string {
return "vultur: planează sus"
}
}
function airShow(flyers: Flyer[]): void {
for (const f of flyers) {
console.log(f.fly())
}
}
airShow([new Sparrow(), new Eagle()])