Respectă promisiunea
Principiul substituției Liskov spune: oriunde codul așteaptă o interfață, fiecare clasă care o implementează trebuie să funcționeze corect. O implementare care se potrivește formal, dar se comportă prost — aruncă excepții, nu face nimic, minte — strică orice funcție care s-a încrezut în interfață.
O interfață este o promisiune. fly() promite zbor. O clasă care implementează fly() aruncând o excepție 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 promisiune. Pinguinii sunt și ei păsări — așa că pare firesc să implementeze interfața... cu un throw, pentru că pinguinii nu pot zbura.
Acum airShow, care funcționează pentru orice pasăre onestă, se prăbușește pe un pinguin. Eroarea 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() pe fiecare pasăre. Vrabia își face numărul; pinguinul aruncă o excepție și doboară tot spectacolul (blocul try/catch doar atenuează căderea, pentru a afișa mesajul).
Ieșire: script PHP
<?php
interface Bird {
public function fly(): string;
}
class Sparrow implements Bird {
public function fly(): string {
return "vrabia: zboară jos";
}
}
// Penguin semnează contractul — dar nu îl poate respecta.
class Penguin implements Bird {
public function fly(): string {
throw new RuntimeException("pinguinii nu pot zbura");
}
}
function airShow(Bird ...$birds): void {
foreach ($birds as $b) {
echo $b->fly(), "\n";
}
}
try {
airShow(new Sparrow(), new Penguin());
} catch (RuntimeException $e) {
echo "spectacolul s-a prăbușit: ", $e->getMessage(), "\n";
}
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: airShow are tipul declarat Flyer, deci airShow(new Penguin()) nu trece verificarea de tip — PHP oprește apelul cu un TypeError înainte ca vreo pasăre să intre în scenă, iar un analizor static semnalează linia chiar înainte ca codul să ruleze.
Fiecare clasă îș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: script PHP
<?php
interface Flyer {
public function fly(): string;
}
interface Swimmer {
public function swim(): string;
}
class Sparrow implements Flyer {
public function fly(): string {
return "vrabia: zboară jos";
}
}
class Penguin implements Swimmer {
public function swim(): string {
return "pinguinul: se scufundă adânc";
}
}
function airShow(Flyer ...$flyers): void {
foreach ($flyers as $f) {
echo $f->fly(), "\n";
}
}
function poolShow(Swimmer ...$swimmers): void {
foreach ($swimmers as $s) {
echo $s->swim(), "\n";
}
}
airShow(new Sparrow());
poolShow(new Penguin());
// airShow(new Penguin()) ar eșua la verificarea de tip cu un TypeError.
Orice Flyer se poate alătura
Câștigul interfețelor oneste: o nouă clasă Eagle se alătură spectacolului aerian fără niciun risc — orice implementează Flyer zboară cu adevărat, deci airShow nu trebuie niciodată să se îndoiască de datele primite.
Ieșire: script PHP
<?php
interface Flyer {
public function fly(): string;
}
class Sparrow implements Flyer {
public function fly(): string {
return "vrabia: zboară jos";
}
}
// Eagle este nouă — și zboară cu adevărat, deci poate fi substituită în siguranță.
class Eagle implements Flyer {
public function fly(): string {
return "acvila: se înalță sus";
}
}
function airShow(Flyer ...$flyers): void {
foreach ($flyers as $f) {
echo $f->fly(), "\n";
}
}
airShow(new Sparrow(), new Eagle());