Respectă promisiunea
Principiul substituției Liskov spune: oriunde codul așteaptă un protocol, fiecare implementare a acelui protocol trebuie să funcționeze corect. O implementare care se potrivește tehnic, dar se comportă greșit — aruncă o excepție, nu face nimic, minte — strică fiecare funcție care a avut încredere în protocol.
Un protocol este o promisiune. fly() promite zborul. O clasă care implementează fly() aruncând o excepție a semnat un contract pe care nu îl poate respecta.
Un înlocuitor care încalcă înțelegerea
Protocolul Bird promite fly(). Vrăbiile se țin de cuvânt. Și pinguinii sunt păsări — așa că pare firesc să implementeze protocolul... cu un raise, pentru că pinguinii nu pot zbura.
Acum air_show, care funcționează pentru orice pasăre cinstită, se prăbușește la un pinguin. Eroarea nu este în air_show — este în înlocuitorul care a încălcat promisiunea.
Pinguinul care strică spectacolul
air_show are încredere în protocol și apelează fly() pentru fiecare pasăre. Vrabia își face numărul; pinguinul aruncă o excepție și oprește tot spectacolul (try/except doar atenuează aterizarea, ca să afișeze mesajul).
Ieșire: program Python complet
from typing import Protocol
class Bird(Protocol):
def fly(self) -> str: ...
class Sparrow:
def fly(self) -> str:
return "vrabia: zboară jos"
# Penguin semnează contractul — dar nu îl poate respecta.
class Penguin:
def fly(self) -> str:
raise RuntimeError("pinguinii nu pot zbura")
def air_show(birds: list[Bird]) -> None:
for b in birds:
print(b.fly())
try:
air_show([Sparrow(), Penguin()])
except RuntimeError as err:
print("spectacolul s-a prăbușit:", err)
Separă promisiunile
Soluția: promite doar ce este adevărat. Împarte promisiunea prea mare î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: un verificator de tipuri (de exemplu, mypy) raportează air_show([Penguin()]) înainte ca programul să ruleze, pentru că un pinguin nu are metoda fly() — simplul python3 ar da eroare abia când se apelează fly().
Fiecare clasă își respectă promisiunea
air_show acceptă Flyer, pool_show acceptă Swimmer. Fiecare animal apare doar acolo unde promisiunea lui este valabilă — substituția este sigură prin construcție.
Ieșire: program Python complet
from typing import Protocol
class Flyer(Protocol):
def fly(self) -> str: ...
class Swimmer(Protocol):
def swim(self) -> str: ...
class Sparrow:
def fly(self) -> str:
return "vrabia: zboară jos"
class Penguin:
def swim(self) -> str:
return "pinguinul: se scufundă adânc"
def air_show(flyers: list[Flyer]) -> None:
for f in flyers:
print(f.fly())
def pool_show(swimmers: list[Swimmer]) -> None:
for s in swimmers:
print(s.swim())
air_show([Sparrow()])
pool_show([Penguin()])
# air_show([Penguin()]) — un verificator de tipuri (de exemplu, mypy) raportează asta
# înainte ca programul să ruleze; simplul python3 dă eroare abia când se apelează fly().
Orice Flyer se poate alătura
Câștigul protocoalelor cinstite: un nou Eagle se alătură spectacolului aerian fără niciun risc — orice implementează Flyer zboară cu adevărat, așa că air_show nu trebuie niciodată să se îndoiască de datele primite.
Ieșire: program Python complet
from typing import Protocol
class Flyer(Protocol):
def fly(self) -> str: ...
class Sparrow:
def fly(self) -> str:
return "vrabia: zboară jos"
# Eagle este nou — și zboară cu adevărat, deci substituția este sigură.
class Eagle:
def fly(self) -> str:
return "vulturul: plutește sus"
def air_show(flyers: list[Flyer]) -> None:
for f in flyers:
print(f.fly())
air_show([Sparrow(), Eagle()])