Cechy dobrego projektu - 4. Zasada open / close
maj 6th, 2009software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification [Bertrand Meyer]
Nasz kod powinien być zamknięty na modyfikację i otwarty na rozszerzanie. Inaczej mówiąc: rozwijając projekt, nie powinniśmy zmieniać istniejącego kodu, a jedynie go rozszerzać.
Gdy zmieniamy coś w istniejącym kodzie, bardzo łatwo zepsuć inną funkcjonalność, która z tego fragmentu kodu korzysta.
Zgodnie z zasadą open/close należy unikać modyfikacji istniejącego kodu.
Przykład:
Piszemy prostą aplikację, która wypisuje zawartość pliku tekstowego na ekran numerując linie pliku.
Do dyspozycji mamy funkcję render_file() która ma za zadanie wypisać na wyjście zawartość pliku z ponumerowanymi liniami. Funkcja render_file() używa klasy SimpleSequence, która umożliwia wygenerowanie kolejnych liczb naturalnych.
class SimpleSequence { protected $i=0; public function next() { return $i++; } } function render_file($file_name) { $seq = new SimpleSequence(); foreach( file($file_name) as $line) { printf("%s: %s", $seq->next(), $line); } }
index.php:
render_file("myfile.txt");
Założenia są spełnione. Nasz program działa prawidłowo.
Jednak co w przypadku, gdy będziemy musieli zmienić jego działanie i numerować linie pliku za pomocą liter “a” - “z”?
Będziemy musieli zmienić zawartość funkcji render_file(), a więc nie jest spełniona zasada open/close.
Jak zrobić to lepiej?
Zmodyfikujmy nieco nasz program przez dodanie interfejsu ISequence.
interface ISequence { public function next(); } class SimpleSequence implements ISequence { protected $i=0; public function next() { return $this->i++; } } function render_file($file_name, ISequence $seq) { foreach( file($file_name) as $line) { printf("%s: %s", $seq->next(), $line); } }
index.php:
render_file("myfile.txt", new SimpleSequence());
Co zyskujemy?
NIE MUSIMY zmieniać ani implementacji metody render_file(), ani klasy SimpleSequence.
Teraz aby wypisać zawartość pliku “numerując” linie kolejnymi literami alfabety wystarczy zaimplementować nową klasę implementującą interfejs ISequence, np. AlphaSequence. Klasa ta za pomocą metody next() będzie zwracać kolejne litery alfabetu.
index.php:
class AlphaSequence implements ISequence { protected $chars = 'abcdefghijklmnopqrstuvwxyz'; protected $i = 0; public function next() { return $this->chars[ $this->i++ % strlen($this->chars) ]; } } render_file("myfile.txt", new AlphaSequence());
To tylko prosty przykład. Tak naprawdę konieczność stosowania tej zasady poznajemy, gdy pracujemy z setkami tysięcy linii kodu. W praktyce wygląda to tak, że każda zmiana istniejącego kodu może powodować nieoczekiwane zmiany w istniejących już elementach naszego projektu (tu bardzo ważna jest zasada “Low coupling, high cohesion” - minimalizacja zależności pomiędzy modułami, maksymalizacja spójności modułów).
