Wyjaśnij zasadę otwartej / zamkniętej

25

Jak już wyjaśniłem, zasada otwarta / zamknięta stwierdza, że ​​raz napisanego kodu nie należy modyfikować (oprócz poprawek błędów). Ale jeśli moje reguły biznesowe ulegną zmianie, czy nie powinienem modyfikować kodu wdrażającego te zmiany? Podejrzewam, że nie rozumiem czegoś na temat zasady, ponieważ nie ma to dla mnie sensu.

Winston Ewert
źródło

Odpowiedzi:

22

Jest to prawdopodobnie najtrudniejsza z solidnych zasad do wyjaśnienia. Pozwól mi spróbować. Wyobraź sobie, że napisałeś klasę faktury, która działa idealnie i nie zawiera błędów. Robi PDF faktury.

Następnie ktoś mówi, że chce faktury HTML z linkami. Nie zmieniasz żadnego kodu na fakturze, aby spełnić to żądanie. Zamiast tego tworzysz inną klasę, fakturę HTML, która robi to, czego teraz chce. Korzystasz z dziedziczenia, abyś nie musiał pisać dużo zduplikowanego kodu w fakturze HTML.

Stary kod, który korzystał ze starej faktury, nie jest uszkodzony ani nie jest w żaden sposób naruszony. Nowy kod może korzystać z faktury HTML. (Jeśli wykonasz także Liskov substitutability , L bryły, możesz nadać instancjom HTMLInvoice istniejący kod, który oczekuje instancji Faktury.) Wszyscy żyją długo i szczęśliwie.

Faktura jest zamknięta dla modyfikacji, otwarta na rozszerzenie. Aby to zadziałało, musisz poprawnie napisać Fakturę, btw.

Kate Gregory
źródło
1
Jeśli reguły biznesowe ulegną zmianie, nie zakłada się, że działa idealnie bez błędów, więc zasada otwarcia / zamknięcia nie ma zastosowania?
JeffO
Sam zmagałem się z tą zasadą i to, co sugeruje Kate, jest w zasadzie tym, co do niej doszedłem. W biznesie próbujesz zaprogramować mniejsze, bardziej elastyczne klasy, abyś mógł z nich dobrze korzystać. Jeśli masz prawidłowe działanie, nie chcesz ich modyfikować. Ale rzadko są w pełni „gotowe”, więc pewne modyfikacje są nieuniknione. Zauważ, że tekst mówi „moduł”, a nie obiekt. Często z powodzeniem stosuję OCP na poziomie funkcji, z ciasnymi funkcjami, które doskonale wykonują jedną rzecz i nigdy nie wymagają zmiany.
CodexArcanum,
1
@Jeff OI rozróżnia naprawianie błędu (gdzie kod nie spełniał pierwotnego wymagania i nikt tego nie chce) i zmienianie wymagań. Jeśli potrzebuję plików PDF, a kod tworzy pliki PDF, nie ma błędu, mimo że teraz chcę HTML (i zwykle ludzie też chcą HTML, nie zamiast tego)
Kate Gregory,
2
@Winston - to miałem na myśli, gdy powiedziałem, że musisz poprawnie pisać faktury. Idealnie byłoby już dość abstrakcyjna faktura i odziedziczyłeś fakturę PDF, oczekując tego. Jeśli nie, musisz raz złamać regułę, aby ustawić się tak, aby nie złamać jej w przyszłości. Tak czy inaczej, przewidywanie przyszłych zmian jest ogromną częścią tego wszystkiego - i to jest część przepisu na „złap i pokrój słonia”.
Kate Gregory
1
Twoje ostatnie zdanie jest najważniejsze. Otwarte / zamknięte to idealna konstrukcja - aby ją osiągnąć, musisz mieć projekt z góry. Nie wszystko musi również spełniać warunki otwarcia / zamknięcia, ale jest to potężne narzędzie, jeśli możesz się tam dostać.
Alex Feinman,
13

Czy czytałeś artykuł The Open-Closed Principle autorstwa kumpli wujka Boba z ObjectMentor? Myślę, że to jedno z lepszych wyjaśnień.

Istnieje wiele heurystyk związanych z projektowaniem obiektowym. Na przykład „wszystkie zmienne składowe powinny być prywatne” lub „należy unikać zmiennych globalnych”, lub „niebezpieczne jest używanie identyfikacji typu w czasie wykonywania (RTTI)”. Jakie jest źródło tych heurystyk? Co sprawia, że ​​są prawdziwe? Czy zawsze są prawdziwe? Ta kolumna bada zasadę projektowania leżącą u podstaw tej heurystyki - zasadę otwartego zamkniętego.

Jak powiedział Ivar Jacobson: „Wszystkie systemy zmieniają się podczas swoich cykli życia. Należy o tym pamiętać, opracowując systemy, które mają trwać dłużej niż pierwsza wersja. ”Jak możemy tworzyć projekty, które są stabilne w obliczu zmian i będą trwać dłużej niż pierwsza wersja? Bertrand Meyer udzielił nam wskazówek już w 1988 r., Kiedy ukształtował słynną już zasadę otwartego zamknięcia. Parafrazując go:

PODMIOTY OPROGRAMOWANIA (KLASY, MODUŁY, FUNKCJE, ITP.) POWINNY BYĆ OTWARTE NA ROZSZERZENIE, ALE ZAMKNIĘTE NA MODYFIKACJĘ.

Gdy pojedyncza zmiana w programie powoduje kaskadę zmian w zależnych modułach, program ten wykazuje niepożądane atrybuty, które kojarzymy z „złym” projektem. Program staje się kruchy, sztywny, nieprzewidywalny i niemożliwy do ponownego użycia. Zasada otwartego zamknięcia atakuje to w bardzo prosty sposób. Mówi, że powinieneś projektować moduły, które nigdy się nie zmieniają . Gdy wymagania się zmieniają, rozszerzasz zachowanie takich modułów, dodając nowy kod, a nie zmieniając stary, który już działa.

Opis

Moduły zgodne z zasadą otwartego i zamkniętego mają dwa podstawowe atrybuty.

  1. Są „otwarte na rozszerzenie”.
    Oznacza to, że zachowanie modułu można rozszerzyć. Możemy sprawić, że moduł będzie zachowywał się w nowy i różny sposób, gdy zmieniają się wymagania aplikacji lub w celu zaspokojenia potrzeb nowych aplikacji.
  2. Są „zamknięte na modyfikacje”.
    Kod źródłowy takiego modułu jest nienaruszony. Nikomu nie wolno dokonywać w nim zmian w kodzie źródłowym.

Wydaje się, że te dwa atrybuty są ze sobą sprzeczne. Normalnym sposobem na rozszerzenie zachowania modułu jest dokonywanie zmian w tym module. Uważa się, że moduł, którego nie można zmienić, ma ustalone zachowanie. Jak rozwiązać te dwa przeciwstawne atrybuty?

Abstrakcja jest kluczem ...

Martijn Verburg
źródło
3
To dobry artykuł wyjaśniający abstrakcję. Należy jednak wziąć pod uwagę podstawową kwestię, a przede wszystkim czy to był dobry projekt abstrakcyjny? Wiele sklepów ma wiele starszych kodów, których jedynym sposobem na zmianę jest „modyfikacja”, a nie „rozszerzenie”. Jeśli tak jest, prawdopodobnie należy to zmienić, ale dopóki tak się nie stanie, utkniesz modyfikując kod.
Michael K,
@Chris, spoko - polecam także książkę „Czysty kod” autorstwa wujka Boba, jeśli lubisz tego rodzaju rzeczy.
Martijn Verburg
@Michael - Całkowicie się zgadzam, to prawie jak konieczność zmiany kodu, aby był on idealny do przetestowania.
Martijn Verburg
Artykuł bardzo ładnie pokazuje znaczenie abstrakcji. Ale nie rozumiem związku między abstrakcjami i nie próbuję nigdy modyfikować modułów po ich napisaniu. Abstrakcja oznacza, że ​​mogę modyfikować moduł X bez konieczności wprowadzania modyfikacji w module Y. Ale czy nie ma sensu tego robić, więc mogę modyfikować moduł X lub moduł Y, jeśli zajdzie taka potrzeba?
Winston Ewert,
1
Łał. Kod jest nieważny? Nigdy nie byłem wielkim fanem wuja Boba. Ta zasada jest pedantyczna, wyjątkowo niepragmatyczna i ma ograniczone połączenie z rzeczywistością.
user949300
12

Odpowiedź Kate Gregory jest bardzo dobra, ale pod inną sytuację, w której nowy wymóg może być spełniony przez stosunkowo niewielką zmianę w istniejącej Invoiceklasie. Załóżmy na przykład, że do faktury PDF należy dodać nowe pole. Według OCP powinniśmy nadal tworzyć nową podklasę, nawet jeśli nowe pole można dodać do istniejącej implementacji, zmieniając kilka wierszy kodu.

W moim rozumieniu OCP odzwierciedla rzeczywistość lat 80. i wczesnych 90., w których projekty często nawet nie korzystały z kontroli wersji, a tym bardziej miały zautomatyzowane testy regresji lub zalety zaawansowanych narzędzi refaktoryzacyjnych. OCP był próbą uniknięcia ryzyka złamania kodu, który został ręcznie przetestowany i wprowadzony do produkcji. Obecnie mamy lepsze sposoby zarządzania ryzykiem uszkodzenia działającego oprogramowania (a mianowicie systemów kontroli wersji, TDD i testów automatycznych oraz narzędzi do refaktoryzacji).

Rogério
źródło
2
Tak, ponieważ w praktyce nie jest możliwe stworzenie klasy, którą można rozszerzyć, aby pasowała do wszystkich możliwych przyszłości, chyba że zabezpieczysz wszystkie metody (co jest do bani, a także narusza zasadę YAGNI, która jest znacznie ważniejsza niż O / C dito).
Martin Wickman,
„Według OCP powinniśmy nadal tworzyć nową podklasę, nawet jeśli nowe pole można dodać do istniejącej implementacji, zmieniając kilka wierszy kodu.”: Naprawdę? Dlaczego nie dodać nowych pól lub nowych metod? Ważne jest to, że dodajesz (przedłużasz) i nie zmieniasz tego, co już tam jest.
Giorgio
Myślę, że zasada ma sens w przypadku standardowych bibliotek / frameworków. Nie chcesz otwierać i modyfikować dobrze ustalonych fragmentów kodu. W przeciwnym razie chodzi o ciągłe refaktoryzowanie i testowanie, testowanie, testowanie.
mastaBlasta
@Giorgio Jasne, dodawanie nowych pól lub metod jest to, co polecam, w większości przypadków. Ale to nie jest rozszerzenie , to „modyfikacja”; cały sens OCP polega na tym, że kod powinien być „zamknięty dla modyfikacji” (tj. bez zmian w istniejącym pliku źródłowym), a jednocześnie „otwarty na rozszerzenie”; a rozszerzenie OCP osiąga się poprzez dziedziczenie implementacji.
Rogério
@ Rogério: Dlaczego definiujesz granicę między rozszerzeniem a modyfikacją na poziomie klasy? Czy jest tego szczególny powód? Wolę ustawić ją na poziomie metody: zmiana metody zmienia zachowanie aplikacji, dodanie (publicznej) metody rozszerza jej interfejs.
Giorgio
6

Osobiście uważam, że tę zasadę należy wziąć ze szczyptą soli. Kod jest organiczny, firmy zmieniają się, a kod zmienia się zgodnie z potrzebami firmy w miarę upływu czasu.

Bardzo trudno mi się skupić na tym, że abstrakcja jest kluczowa. Co jeśli abstrakcja była pierwotnie nieprawidłowa? Co się stanie, jeśli funkcja biznesowa ulegnie znaczącej zmianie?

Ta zasada zasadniczo zapewnia, że ​​ORYGINALNE intencje i zachowanie projektu nigdy się nie zmieniają. To prawdopodobnie działa dla tych, którzy mają publiczne interfejsy API, a ich klienci mają problemy z nadążaniem za nowymi wersjami i niektórymi innymi przypadkowymi przypadkami. Jeśli jednak firma jest właścicielem CAŁEGO kodu, podważam tę zasadę.

Dobre testowanie kodu powinno sprawić, że refaktoryzacja bazy kodu będzie dziecinnie prosta. Oznacza to, że nie ma problemu z pomyłkami - twoje testy pomogą ci lepiej zaprojektować.

Mówiąc, że jeśli nie ma żadnych testów, to zasada jest zdrowa.

użytkownik126776
źródło