Dlaczego (/ czy) Bertrand Meyer uważa, że ​​podklasa jest jedynym sposobem na rozszerzenie „zamkniętego” modułu?

19

W Meyer Object-Oriented Software Construction (1988) definiuje zasadę otwartego / zamkniętego w następujący sposób:

  • Mówi się, że moduł jest otwarty, jeśli jest nadal dostępny do rozszerzenia. Na przykład powinna istnieć możliwość dodawania pól do zawartych w nim struktur danych lub nowych elementów do zestawu funkcji, które wykonuje.
  • Moduł zostanie uznany za zamknięty, jeśli będzie dostępny do użytku przez inne moduły. Zakłada się, że moduł otrzymał dobrze zdefiniowany, stabilny opis (interfejs w sensie ukrywania informacji).

Dalej mówi:

Jeśli ponownie otworzysz moduł, musisz również ponownie otworzyć wszystkich jego klientów, aby je zaktualizować, ponieważ korzystają ze starej wersji. … [Ten problem] pojawia się za każdym razem, gdy moduł musi zostać rozszerzony o nową funkcję lub element danych, powodując zmiany w bezpośrednich i pośrednich klientach. ... Przy klasycznym podejściu do projektowania i programowania nie ma możliwości pisania modułów, które są zarówno otwarte, jak i zamknięte.

Rozwiązaniem tego dylematu Meyera jest: nigdy nie rozszerzaj modułu biblioteki poprzez modyfikację istniejących klas; zamiast tego napisz nowy moduł, który podklasuje istniejące klasy, i spraw, aby nowi klienci byli zależni od tego nowego modułu.

Teraz, w 1988 roku, pisałem programy zabawkowe (proceduralne) w Turbo Pascal i Blankenship Basic, a moje doświadczenie zawodowe w XXI wieku dotyczy JVM, CLR i języków dynamicznych, więc nie wiem, co miał na myśli Meyer przez „klasyczne podejście do projektowania i programowania”.

Jeden konkretny przykład Meyera, dlaczego moduły klienta muszą zostać ponownie otwarte (instrukcja switch w wyliczeniu, która ma teraz więcej członków, wymagająca większej liczby przypadków) wydaje się dość rozsądna, ale nie uzasadnia prawie twierdzenia, że ​​za każdym razem , gdy dodajesz funkcjonalność do biblioteki moduł, musisz zaktualizować wszystkich swoich klientów .

Czy istnieje historyczny powód, dla którego twierdzenie to wydawało się oczywiste w 1988 roku? Czy, powiedzmy, dodanie funkcji lub struktur danych do biblioteki statycznej C zmieniło układ tak, że nawet przy API kompatybilnych wstecz, klienci musieli zostać ponownie skompilowani? A może Meyer tak naprawdę mówi tylko o mechanizmie wymuszania wstecznej kompatybilności API?

David Moles
źródło
3
Interesujące pytanie! Mam wrażenie, że odpowiedź będzie w jakiś sposób powiązana z podstawową różnicą między abstrakcyjnymi typami danych a obiektową abstrakcją danych , które są dwoma dominującymi mechanizmami pozyskiwania danych w programowaniu modułowym (co Betrand Meyer nazywa „podejściem klasycznym” ”) i odpowiednio programowanie obiektowe (przeczytaj komentarze!).
Jörg W Mittag
To jest dziwne. Wydaje się to rażąco sprzeczne z rzeczywistością (nawet w 1988 r.). Ponadto jego zalecane podejście doprowadziłoby do niepomyślnego rozprzestrzeniania się modułów.
@ dan1111: Podejście Eiffla do dziedziczenia, w tym między innymi podejście do wielokrotnego dziedziczenia różni się od C ++, Java, C # itp., więc nie jest zaskakujące, że podejście jest inne. W końcu opracował Eiffla, aby w końcu poprzeć jego poglądy na temat OO.
Jörg W Mittag

Odpowiedzi:

18

O ile wiem, na to pytanie odpowiedział sam Bertrand Meyer, a odpowiedź brzmi: to stwierdzenie nie jest dokładne. Z klasycznego podejścia do projektowania i programowania, tam rzeczywiście może być sposobem zapisu modułów, które są otwarte i zamknięte.

Aby się tego dowiedzieć, musisz przestudiować drugie wydanie tej książki (opublikowanej dziewięć lat później, w 1997 r.). Według przedmowy do drugiego wydania jest

nie aktualizacja, ale wynik gruntownej przeróbki. Nie zmieniono akapitu oryginalnej wersji. (Właściwie to nie jedna linia.)

W szczególności minęło stwierdzenie, które Cię myli. W rozdziale „§3.3 Pięć zasad” nadal znajduje się rozdział dotyczący zasad otwartych i zamkniętych , a dalsze omówienie tego tematu znajduje się w „§14.7 Wprowadzenie do dziedziczenia”, ale stwierdzenia z pierwszego wydania już go nie ma.

To, co tam jest, skupia się na tym, jak jest wygodniejsze i idiomatyczne w podejściu OO, w przeciwieństwie do wcześniejszych sposobów,

Dzięki dziedziczeniu programiści OO mogą przyjąć znacznie bardziej inkrementalne podejście do tworzenia oprogramowania, niż było to możliwe w przypadku wcześniejszych metod ... (§3.3)

Ten podwójny wymóg (otwarty i zamknięty) wygląda jak dylemat, a klasyczne struktury modułów nie dają żadnych wskazówek. Ale dziedziczenie rozwiązuje to. Klasa jest zamknięta, ponieważ może być kompilowana, przechowywana w bibliotece, bazowana i używana przez klasy klienta. Ale jest również otwarty, ponieważ każda nowa klasa może używać go jako elementu nadrzędnego, dodając nowe funkcje i ponownie udostępniając funkcje odziedziczone; w tym procesie nie trzeba zmieniać oryginału ani przeszkadzać jego klientom ... (§ 14.7)

Ponieważ wydaje się, że zastanawiasz się także, co znaczyło tutaj „podejście klasyczne” Meyera, możesz je wyjaśnić w § 4.7 Tradycyjne konstrukcje modułowe . W tej sekcji wyjaśniono, że oznaczają one „biblioteki procedur” i „pakiety” (w tym drugim przypadku autor twierdzi, że termin pochodzi od Ady i wspomina o innych językach posiadających tę funkcję - klastry w CLU i moduły w Modula).

Jeśli się nad tym zastanowić, początkowo żadne z tych podejść nie miało na celu pomocy w pisaniu kodu zgodnego z zasadą otwartego zamknięcia. Może to doprowadzić autora do nieco przedwczesnej oceny, którą później poprawiono w drugim wydaniu.


Jeśli chodzi o to, co konkretnie skłoniło autora do zmiany zdania na temat tego stwierdzenia między pierwszym a drugim wydaniem, myślę, że można znaleźć odpowiedź w samej książce, a mianowicie w części F: Zastosowanie metody w różnych językach i środowiskach . w tym rozdziale autor omawia, w jaki sposób można stosować metody obiektowe w starszych językach:

Języki klasyczne, takie jak Fortran, wcale nie są OO, ale ludzie, którzy nadal muszą z nich korzystać ... mogą chcieć zastosować tyle pomysłów OO, ile to możliwe, w granicach tych starszych podejść.

W szczególności w tej części Meyer szczegółowo wyjaśnia, w jaki sposób można wprowadzić dziedziczenie (z pewnymi zastrzeżeniami i ograniczeniami, ale nadal) w C, a nawet w Fortranie.

Widzisz, to naprawdę wymaga rewizji tego stwierdzenia z pierwszego wydania. Praktycznie niemożliwe jest wyjaśnienie, jak pogodzić „z klasycznym podejściem ... nie ma mowy” z realistycznymi przykładami, jak dokładnie można to zrobić.

komar
źródło
Ciekawe i na pewno będę musiał spróbować zdobyć drugą edycję, ale nadal nie jest dla mnie jasne, dlaczego nawet „klasyczna” biblioteka nie-OO nie mogła dodać (przynajmniej niektórych rodzajów) funkcji bez zakłócania jej klienci.
David Moles,
@DavidMoles rzecz jest taka, że może , a ostatnia część mojej odpowiedzi to wyjaśnia i że sam Meyer zdał sobie z tego sprawę (kiedy przerobił drugą edycję), a nawet podał przykłady tego, jak można to zrobić. „Co do tego, co konkretnie zmusiło autora do zmiany zdania ...” itd.
gnat
Hmm Nie widzę „wersji 2 tej biblioteki, która zastępuje wersję 1 i jest z nią kompatybilna wstecz, dodaje następujące funkcje…” jako „dziedziczenie”, z wyjątkiem najszerszego możliwego sposobu koncepcyjnego.
David Moles,
(Dla mnie dziedziczenie oznacza, że ​​wersja 1 jest nadal dostępna i wywoływana przez wersję 2.)
David Moles,
@DavidMoles zamień na wersję 2 (jak w, zmień kod źródłowy i ponownie skompiluj ) nie kwalifikuje się jako „zamknięty dla modyfikacji”, możesz po prostu sprawdzić to w artykule w Wikipedii : „byt może zezwolić na rozszerzenie jego zachowania bez modyfikowania kodu źródłowego ... ”
komar