Rozumiem intencję zasady otwartego zamknięcia. Ma to na celu zmniejszenie ryzyka zepsucia czegoś, co już działa, podczas modyfikowania go, poprzez nakazanie próby rozszerzenia bez modyfikacji.
Miałem jednak pewne problemy ze zrozumieniem, w jaki sposób ta zasada jest stosowana w praktyce. W moim rozumieniu istnieją dwa sposoby zastosowania tego. Przed i po możliwej zmianie:
Przedtem: programuj abstrakcje i „przewiduj przyszłość” tak bardzo, jak potrafisz. Na przykład metoda
drive(Car car)
będzie musiała ulec zmianie, jeśliMotorcycle
s zostaną dodane do systemu w przyszłości, więc prawdopodobnie narusza OCP. Jednak metodadrive(MotorVehicle vehicle)
ta prawdopodobnie nie będzie musiała ulec zmianie w przyszłości, więc jest zgodna z OCP.Jednak dość trudno jest przewidzieć przyszłość i wiedzieć z wyprzedzeniem, jakie zmiany zostaną wprowadzone w systemie.
Po: gdy potrzebna jest zmiana, rozszerz klasę zamiast modyfikować jej bieżący kod.
Ćwiczenie nr 1 nie jest trudne do zrozumienia. Jednak jest to praktyka nr 2, że mam problem ze zrozumieniem, jak się zgłosić.
Na przykład (wziąłem go z video na YouTube): powiedzmy, że mamy metodę w klasie, który akceptuje CreditCard
obiekty: makePayment(CraditCard card)
. Jeden dzień Voucher
są dodawane do systemu. Ta metoda ich nie obsługuje, więc należy go zmodyfikować.
Wdrażając metodę, nie udało nam się przewidzieć przyszłości i program w bardziej abstrakcyjny sposób (np. makePayment(Payment pay)
Teraz musimy zmienić istniejący kod.
Ćwiczenie nr 2 mówi, że powinniśmy dodać tę funkcjonalność, rozszerzając zamiast modyfikować. Co to znaczy? Czy powinienem podklasować istniejącą klasę zamiast po prostu zmieniać jej istniejący kod? Czy powinienem zrobić wokół niego jakieś opakowanie, aby uniknąć przepisywania kodu?
Czy też zasada nie odnosi się nawet do „jak poprawnie zmodyfikować / dodać funkcjonalność”, ale raczej do „jak uniknąć konieczności wprowadzania zmian w pierwszej kolejności (tj. Programu do abstrakcji)?
źródło
Odpowiedzi:
Zasady projektowania zawsze muszą być zrównoważone. Nie możesz przewidzieć przyszłości, a większość programistów robi to okropnie, kiedy próbują. Dlatego mamy zasadę trzech , która dotyczy przede wszystkim powielania, ale dotyczy również refaktoryzacji w przypadku innych zasad projektowania.
Gdy masz tylko jedną implementację interfejsu, nie musisz się zbytnio przejmować OCP, chyba że jest to krystalicznie jasne, gdzie miałyby miejsce jakieś rozszerzenia. W rzeczywistości często tracisz jasność podczas próby przeprojektowania w tej sytuacji. Po jednorazowym przedłużeniu zmieniamy go tak, aby uczynić go przyjaznym dla OCP, jeśli jest to najłatwiejszy i najczystszy sposób. Po rozszerzeniu go na trzecią implementację pamiętaj o jego refaktoryzacji, biorąc pod uwagę OCP, nawet jeśli wymaga to nieco więcej wysiłku.
W praktyce, gdy masz tylko dwie implementacje, refaktoryzacja po dodaniu trzeciej zwykle nie jest zbyt trudna. To wtedy, gdy pozwalasz mu przekroczyć ten punkt, utrzymanie go staje się trudne.
źródło
Myślę, że patrzysz zbyt daleko w przyszłość. Rozwiąż bieżący problem w elastyczny sposób, przylegający do otwarcia / zamknięcia.
Powiedzmy, że musisz zaimplementować
drive(Car car)
metodę. W zależności od języka masz kilka opcji.W przypadku języków obsługujących przeciążanie (C ++), po prostu użyj
drive(const Car& car)
W pewnym momencie możesz potrzebować
drive(const Motorcycle& motorcycle)
, ale nie będzie to przeszkadzałodrive(const Car& car)
. Nie ma problemu!W przypadku języków, które nie obsługują przeciążania (Cel C), należy podać nazwę typu w metodzie
-driveCar:(Car *)car
.W pewnym momencie możesz potrzebować
-driveMotorcycle:(Motorcycle *)motorcycle
, ale znowu nie będzie to przeszkadzało.Pozwala
drive(Car car)
to być zamknięte na modyfikacje, ale jest otwarte na rozszerzenie na inne typy pojazdów. To minimalistyczne planowanie przyszłości, które pozwala wykonać pracę dzisiaj, ale powstrzymuje cię przed blokowaniem się w przyszłości.Próba wyobrażenia sobie najbardziej podstawowych typów, których potrzebujesz, może doprowadzić do nieskończonego regresu. Co się dzieje, gdy chcesz prowadzić Segue, rowerem lub odrzutowcem Jumbo. Jak zbudować jeden ogólny typ abstrakcyjny, który może uwzględniać wszystkie urządzenia, z których ludzie korzystają i korzystają z mobilności?
źródło
Chodzi również o to, aby nie zniszczyć wszystkich obiektów, które korzystają z tej metody, nie zmieniając zachowania już istniejących obiektów. Gdy obiekt zareklamuje zmianę zachowania, jest to ryzykowne, ponieważ zmieniasz znane i oczekiwane zachowanie obiektu, nie wiedząc dokładnie, czego inne obiekty oczekują od tego zachowania.
Tak.
„Akceptuje tylko karty kredytowe” jest zdefiniowane jako część zachowania tej klasy poprzez jej interfejs publiczny. Programista oświadczył światu, że metoda tego obiektu przyjmuje tylko karty kredytowe. Zrobiła to za pomocą niezbyt jasnej nazwy metody, ale już zrobione. Reszta systemu polega na tym.
W tamtym czasie mogło to mieć sens, ale teraz, jeśli trzeba to zmienić, powinieneś stworzyć nową klasę, która akceptuje rzeczy inne niż karty kredytowe.
Nowe zachowanie = nowa klasa
Na marginesie - dobrym sposobem przewidywania przyszłości jest zastanowienie się nad nazwą, którą nadałeś metodzie. Czy podałeś naprawdę ogólną nazwę brzmiącą metodę, taką jak makePayment, dla metody z określonymi regułami w metodzie co do tego, jaką dokładnie płatności może dokonać? To zapach kodu. Jeśli masz określone reguły, należy je wyraźnie zaznaczyć w nazwie metody - makePayment powinien być makeCreditCardPayment. Zrób to, gdy piszesz obiekt po raz pierwszy, a inni programiści ci za to podziękują.
źródło