Pytanie Zasada Open / Closed - jak połączyć się z nowymi wersjami?


Próbuję uchwycić zasadę Open / Closed (w moim przypadku dla PHP, ale to nie ma większego znaczenia).

Sposób w jaki to rozumiem jest taki, że klasa jest nigdy otwarte do modyfikacji. Tylko do naprawiania błędów. Jeśli chciałbym dodać nowy kod do klasy, musiałbym utworzyć nowy i rozszerzyć "starą" klasę. To jedyny sposób, w jaki mogę dodać do niego nowy kod.

W pewnym sensie widzę zalety tego. Ponieważ w zasadzie tworzysz jakiś system wersjonowania, w którym stary kod zawsze działa, ale zawsze możesz spróbować użyć również nowych klas.

Ale jak to działa w praktyce? Mam na myśli, załóżmy, że mam następujące klasy:

class MyObject
{
    public function doSomething()
    {
        echo 'Im doing something';
    }
}

Więc gdzieś prawdopodobnie tworzę tę klasę:

$obj = new MyObject();

Ale potem decyduję, że dobrze jest mieć inną metodę w tym obiekcie. Więc mogę zrobić coś innego. Zgodnie z OCP nie mogę modyfikować klasy. Więc muszę stworzyć nowy, który rozciąga się na stary?

Pierwszy problem. Jak mogę nazwać nową klasę? Ponieważ nie jest to kompletnie nowy obiekt. Lubić. obiekt użytkownika jest obiektem użytkownika. Nie mogę nagle nadać mu zupełnie innej nazwy tylko dlatego, że potrzebuje innej metody. W każdym razie, tworzę nową klasę:

class MyNewObject extends MyObject
{
    public function doSomethingElse()
    {
        echo 'Im doing something else now';
    }
}

Teraz oznacza to również, że muszę zmienić linię kodu, w której stworzyłem klasę "MyObject" i zastąpiłem ją klasą "MyNewObject", prawda ..? A jeśli to zrobione w więcej niż jednym miejscu, to muszę przeszukać mój kod źródłowy ... (Pomyśl o metodzie w klasie kontrolera, która prawie zawsze używa "nowego" słowa kluczowego do utworzenia instancji niektórych klas).

To samo dotyczy w zasadzie dziedziczenia. Musiałbym znaleźć każdą klasę, która odziedziczy starą klasę i zastąpić ją nową klasą.


Więc zasadniczo moje pytania to:

Jak nazywasz nowe klasy, które mają nowe metody? Właśnie dlatego, że dodałem kilka nowych funkcji, nie oznacza to, że mogę nadać klasie zupełnie nową nazwę ...

A co jeśli "stare" klasy są tworzone (lub dziedziczone) z wielu miejsc? Wtedy musiałbym znaleźć wszystkie te miejsca ... Gdzie jest zysk?


12
2018-05-23 20:12


pochodzenie


$x = new MyObj_version_3.14159265()? mmmmm. ciasto. - Marc B
Zauważ, że istnieją dwa sposoby interpretacji Zasada otwarta / zamknięta. Wydaje się, że myślisz przede wszystkim o starszym (Meyers), podczas gdy ten przypadek użycia jest zwykle obsługiwany obecnie przez delegację. - Francis Avila
Na to pytanie łatwiej byłoby odpowiedzieć bardziej realistycznym przykładem. Nazewnictwo itp. Wydaje mi się bardziej oczywiste. - Fuhrmanator


Odpowiedzi:


Zasada Open Closed nie jest przeznaczona do stosowania jako rodzaj systemu kontroli wersji. Jeśli naprawdę musisz wprowadzić zmiany w klasie, a następnie wprowadzić te zmiany. Nie musisz tworzyć nowych klas i zmieniać wszystkich miejsc, w których utworzono stare.

Punktem zasady Open Closed jest to, że dobrze zaprojektowany system nie powinien wymagać zmiany istniejącej funkcjonalności w celu dodania nowych funkcji. Jeśli dodajesz nową klasę do systemu, nie musisz przeszukiwać całego kodu, aby znaleźć miejsca, do których powinieneś odwołać się do tej klasy lub mieć specjalne przypadki.

Jeśli projekt twojej klasy nie jest wystarczająco elastyczny, aby obsłużyć jakąś nową funkcjonalność, zmień kod w swojej klasie. Ale kiedy zmieniasz kod, spraw, by był elastyczny, abyś mógł obsługiwać podobne zmiany w przyszłości bez zmian kodu. Ma to być polityka projektowa, a nie zestaw kajdanek, aby zapobiec wprowadzaniu zmian. Dzięki dobrym decyzjom projektowym, z biegiem czasu twój istniejący kod będzie wymagał coraz mniejszej ilości zmian, kiedy dodasz nową funkcjonalność do systemu. To proces iteracyjny.


3
2018-05-23 20:59





Twierdzę, że dodając funkcję, nie modyfikujesz zachowania klasy.

We wszystkich przypadkach, w których funkcja doSomething () jest obecnie wywoływana w Twojej aplikacji, dodanie do klasy doSomethingElse () nie przyniesie efektu. Ponieważ nie zmieniasz funkcji DoSomething (), zachowanie jest takie samo jak wcześniej.

Po ustaleniu, że implementacja doSomething () nie powoduje jej przecięcia w pewnych okolicznościach, można rozszerzyć klasę i przesłonić doSometing (). Znowu oryginał nadal zachowuje się tak samo jak zawsze, ale teraz masz nową funkcję doSomething () do pracy również.

Zdaję sobie sprawę, że jest to sprzeczne ze ścisłą definicją otwartej / zamkniętej, ale to jest prawdziwy świat i tak zinterpretowałem tę zasadę w moim kodeksie.


3
2018-05-23 20:32



Zgadzam się, dodanie nowej metody to nie takie zerwanie. Nie w podanym przykładzie. Ale jeśli potrzebuję zrobić jakiś kod łamiący zmiany w metodzie, to musiałbym stworzyć nową klasę, która robi to, co chcę. Ale moje dwa pytania nadal pozostają. Jak mogę nazwać nową klasę? Czy to znaczy, że muszę przeszukać cały mój kod, aby dowiedzieć się, gdzie stworzyłem starą klasę, aby zmienić ją na nową? To ostatnie przeszkadza mi najbardziej w rzeczywistości ... Dlatego nie widzę korzyści w tej zasadzie. - w00
Powinieneś pomyśleć o tym na odwrót: jeśli twoja podklasa nie ma oczywistej nazwy, być może nie powinieneś jej robić! Podklas nie powinien być używany do naprawiania uszkodzonych klas - uszkodzona klasa powinna być naprawiona, a kod wywoływania, który zależy od złamanego zachowania, również powinien zostać naprawiony. (To powiedziawszy, czasami trzeba podklasy naprawić zepsutą klasę, ale to zwykle dlatego, że nie można kontrolować klasy nadrzędnej lub jej kodu wywołującego.) - Francis Avila
BTW, dwa powody, dla których warto stworzyć podklasę: stworzyć nową implementację bardziej abstrakcyjnej klasy bazowej (polimorficzne dziedziczenie) lub stworzyć podtyp, który ma dodatkową funkcjonalność, ale który jest posłuszny Liskov Podstawowa zasada. W obu przypadkach nowa nazwa powinna być oczywista. - Francis Avila


Musisz utworzyć konstruktor w klasie MyNewObject, która wywołuje konstruktor klasy nadrzędnej:

function __construct() {

    parent::__construct();

}

W ten sposób możesz utworzyć instancję nowej klasy i nadal mieć dostęp do wszystkich funkcjonalności rozszerzonej.

Możesz także zastąpić dowolną funkcję w klasie nadrzędnej (o ile nie jest ona oznaczona jako ostateczna).

Więc możesz zrobić:

$newObj = new MyNewObject();

$newObj->doSomething();

$newObj->doSomethingElse();

0
2018-05-23 20:22



Tak, wiem, że muszę zadzwonić do konstruktora rodziców. W przeciwnym razie dziedziczenie nie byłoby zbyt użyteczne :) --- Ale w twoim przykładzie tworzysz także instancję "MyNewObject" zamiast "MyObject". Co sprawia, że ​​istnieje możliwość, ponieważ chcesz korzystać z nowej funkcjonalności. Ale to także oznacza, że ​​musisz zmienić każdy wiersz w kodzie, w którym kiedyś stworzyłeś "MyObject". Musiałbyś to teraz zmienić na "MyNewObject". To naprawdę nie jest dla mnie zbyteczne, jeśli musisz przeszukać cały kod, aby zmienić stare instancje na nowe. - w00
Tak, widzę twój punkt widzenia. Wybacz mi, zakładając, że jesteś trochę nowy w OOP. - Steven1978