Deschis pentru extindere, închis pentru modificare
Principiul deschis/închis spune: codul trebuie să fie deschis pentru extindere — se poate adăuga comportament nou — dar închis pentru modificare — codul existent, funcțional și testat nu este editat pentru asta.
Fiecare editare a unui cod funcțional este o șansă de a-l strica. Când adăugarea unei funcționalități înseamnă doar adăugarea de cod nou, codul vechi rămâne verificat.
Instrumentul Python pentru asta este protocolul: o clasă construită pe typing.Protocol care enumeră metodele pe care trebuie să le aibă un tip. Orice clasă cu metode potrivite respectă automat protocolul — nu se declară nimic — așa că o funcție care depinde de un protocol nu trebuie modificată niciodată când se adaugă o clasă nouă.
Instrucțiunea match care crește la nesfârșit
Acest calculator de arie folosește un match pe tipul formei. Funcționează — până apare următoarea formă. Triunghi? Editezi match-ul. Pentagon? Îl editezi din nou. Funcția nu este niciodată terminată, iar fiecare editare pune în pericol formele care deja funcționau.
Un match pe tipul formei
total_area trebuie să cunoască fiecare tip de formă. Adăugarea uneia înseamnă modificarea acestei funcții testate — exact lucrul împotriva căruia avertizează principiul.
Ieșire: program Python complet
import math
from dataclasses import dataclass
@dataclass
class Shape:
kind: str
width: float = 0
height: float = 0
radius: float = 0
# total_area trebuie să cunoască fiecare tip — și crește cu fiecare tip nou.
def total_area(shapes):
total = 0.0
for s in shapes:
match s.kind:
case "circle":
total += math.pi * s.radius * s.radius
case "rectangle":
total += s.width * s.height
return total
shapes = [
Shape(kind="circle", radius=2),
Shape(kind="rectangle", width=3, height=4),
]
print(f"aria totală: {total_area(shapes):.2f}")
Extindere printr-un protocol
Soluția: fiecare formă își cunoaște propria arie. Protocolul Shape cere o singură metodă, area(), iar total_area doar adună ce primește — nu îl mai interesează ce forme sunt.
Acum privește principiul în acțiune: o formă complet nouă, Triangle, se adaugă, iar total_area nu este atinsă. Extindere fără modificare.
Fiecare formă își calculează propria arie
total_area depinde doar de protocolul Shape. match a dispărut — și odată cu el, nevoia de a mai edita vreodată această funcție.
Ieșire: program Python complet
import math
from dataclasses import dataclass
from typing import Protocol
class Shape(Protocol):
def area(self) -> float: ...
@dataclass
class Circle:
radius: float
def area(self) -> float:
return math.pi * self.radius * self.radius
@dataclass
class Rectangle:
width: float
height: float
def area(self) -> float:
return self.width * self.height
# total_area depinde doar de protocol — fără match, nimic de editat.
def total_area(shapes: list[Shape]) -> float:
total = 0.0
for s in shapes:
total += s.area()
return total
shapes: list[Shape] = [
Circle(radius=2),
Rectangle(width=3, height=4),
]
print(f"aria totală: {total_area(shapes):.2f}")
Adaugă o formă nouă, fără să atingi nimic
Triangle este cod complet nou: o clasă de date (dataclass) și metoda ei area(). total_area, Circle și Rectangle sunt exact cum erau — deschise pentru extindere, închise pentru modificare.
Ieșire: program Python complet
import math
from dataclasses import dataclass
from typing import Protocol
class Shape(Protocol):
def area(self) -> float: ...
@dataclass
class Circle:
radius: float
def area(self) -> float:
return math.pi * self.radius * self.radius
@dataclass
class Rectangle:
width: float
height: float
def area(self) -> float:
return self.width * self.height
# Triangle este cod NOU — tot ce este mai sus rămâne neatins.
@dataclass
class Triangle:
base: float
height: float
def area(self) -> float:
return self.base * self.height / 2
def total_area(shapes: list[Shape]) -> float:
total = 0.0
for s in shapes:
total += s.area()
return total
shapes: list[Shape] = [
Circle(radius=2),
Rectangle(width=3, height=4),
Triangle(base=6, height=5),
]
print(f"aria totală: {total_area(shapes):.2f}")