Jedną z zasad OOP, na które natrafiłem, jest: -Ekapsuluj, co się różni.
Rozumiem, czym jest dosłowne znaczenie tego wyrażenia, tzn. Ukryj to, co się różni. Nie wiem jednak, jak dokładnie przyczyniłoby się to do lepszego projektu. Czy ktoś może to wyjaśnić na dobrym przykładzie?
design
design-patterns
object-oriented
encapsulation
Haris Ghauri
źródło
źródło
I don't know how exactly would it contribute to a better design
Hermetyzowanie szczegółów dotyczy luźnego sprzężenia między „modelem” a szczegółami implementacji. Im mniej „model” jest powiązany ze szczegółami implementacji, tym bardziej elastyczne jest rozwiązanie. I ułatwia to ewolucję. „Abstrahuj od szczegółów”.Odpowiedzi:
Możesz napisać kod, który wygląda następująco:
lub możesz napisać kod, który wygląda następująco:
Jeśli to, co zmienia się, jest hermetyzowane, nie musisz się tym martwić. Po prostu martwisz się o to, czego potrzebujesz i cokolwiek używasz, zastanawiasz się, jak zrobić to, czego naprawdę potrzebujesz, w zależności od tego, co jest różne.
Hermetyzuj to, co się różni, i nie musisz rozprowadzać kodu, który dba o to, co się zmienia. Po prostu ustawisz zwierzaka na określony typ, który umie mówić jako ten typ, a następnie możesz zapomnieć, który typ i traktować go jak zwierzaka. Nie musisz pytać, jaki typ.
Możesz pomyśleć, że typ jest enkapsulowany, ponieważ do uzyskania dostępu jest wymagany getter. Ja nie. Getter nie jest tak naprawdę enkapsulowany. Po prostu drżą, gdy ktoś złamie twoje kapsułkowanie. Są ładnym dekoratorem przypominającym aspekt haczyka, który jest najczęściej używany jako kod debugowania. Bez względu na to, jak go pokroisz, nadal ujawniasz typ.
Możesz spojrzeć na ten przykład i pomyśleć, że łączę polimorfizm i enkapsulację. Nie jestem. Łączę „co się zmienia” i „szczegóły”.
Fakt, że twoje zwierzę jest psem, jest szczegółem. Taki, który może się dla ciebie różnić. Taki, który może nie. Ale na pewno taki, który może różnić się w zależności od osoby. O ile nie wierzymy, że to oprogramowanie będzie kiedykolwiek używane tylko przez miłośników psów, mądrze jest traktować psa jako szczegół i obudować go. W ten sposób niektóre części systemu są błogo nieświadome psa i nie zostaną naruszone, gdy połączymy się z „papugami jesteśmy my”.
Oddziel, oddziel i ukryj szczegóły od reszty kodu. Nie pozwól, aby wiedza o szczegółach rozprzestrzeniła się w twoim systemie, a będziesz dobrze przestrzegać „enkapsulacji tego, co się zmienia”.
źródło
„Różni się” oznacza tutaj „może się zmieniać z czasem ze względu na zmieniające się wymagania”. Jest to podstawowa zasada projektowania: Oddzielanie i izolowanie fragmentów kodu lub danych, które mogą wymagać osobnej zmiany w przyszłości. Jeśli zmieni się pojedynczy wymóg, najlepiej powinien wymagać od nas zmiany powiązanego kodu w jednym miejscu. Ale jeśli baza kodu jest źle zaprojektowana, tj. Silnie połączona i logika dla wymagań rozłożonych w wielu miejscach, zmiana będzie trudna i będzie wiązać się z wysokim ryzykiem wywołania nieoczekiwanych efektów.
Załóżmy, że masz aplikację, która korzysta z kalkulacji podatku od sprzedaży w wielu miejscach. Jeśli zmieni się stawka podatku od sprzedaży, co wolisz:
stawka podatku jest dosłownie zakodowana wszędzie w aplikacji, w której naliczany jest podatek od sprzedaży.
stawka podatku od sprzedaży jest globalną stałą, która jest stosowana wszędzie w aplikacji, w której naliczany jest podatek od sprzedaży.
istnieje jedna metoda o nazwie,
calculateSalesTax(product)
która jest jedynym miejscem, w którym stosowana jest stawka podatku od sprzedaży.stawka podatku od sprzedaży jest określona w pliku konfiguracyjnym lub polu bazy danych.
Ponieważ stawka podatku od sprzedaży może ulec zmianie w wyniku decyzji politycznej niezależnej od innych wymagań, wolimy, aby była ona izolowana w konfiguracji, dzięki czemu można ją zmienić bez wpływu na kod. Ale możliwe jest również, że logika obliczania podatku od sprzedaży może ulec zmianie, np. Różne stawki dla różnych produktów, dlatego też chcielibyśmy, aby logika obliczeń była zamknięta. Stała globalna może wydawać się dobrym pomysłem, ale w rzeczywistości jest zła, ponieważ może zachęcać do korzystania z podatku od sprzedaży w różnych miejscach programu, a nie w jednym miejscu.
Rozważmy teraz inną stałą, Pi, która jest również używana w wielu miejscach kodu. Czy obowiązuje ta sama zasada projektowania? Nie, ponieważ Pi się nie zmieni. Wyodrębnienie go do pliku konfiguracyjnego lub pola bazy danych po prostu wprowadza niepotrzebną złożoność (a wszystko inne jest równe, preferujemy najprostszy kod). Sensowne jest nadanie jej globalnej stałej zamiast stałego kodowania w wielu miejscach, aby uniknąć niespójności i poprawić czytelność.
Chodzi o to, że jeśli spojrzymy tylko na to, jak program działa teraz , stawka podatku od sprzedaży i Pi są równoważne, oba są stałymi. Dopiero gdy zastanowimy się, co może się różnić w przyszłości , zdamy sobie sprawę, że musimy traktować je inaczej w projekcie.
Ta zasada jest w rzeczywistości dość głęboka, ponieważ oznacza, że musisz spojrzeć poza to, co ma dziś zrobić baza kodu , a także wziąć pod uwagę siły zewnętrzne, które mogą spowodować jej zmianę, a nawet zrozumieć różnych interesariuszy stojących za wymaganiami.
źródło
Obie obecne odpowiedzi wydają się tylko częściowo trafiać w sedno i koncentrują się na przykładach, które przesłaniają podstawową ideę. Nie jest to również (wyłącznie) zasada OOP, ale ogólnie zasada projektowania oprogramowania.
Tym, co „różni się” w tym wyrażeniu, jest kod. Christophe ma rację mówiąc, że zwykle jest to coś, co może się różnić, to znaczy, że często tego oczekujesz . Celem jest ochrona się przed przyszłymi zmianami w kodzie. Jest to ściśle związane z programowaniem w interfejsie . Jednak Christophe błędnie ogranicza to do „szczegółów implementacji”. W rzeczywistości wartość tej porady często wynika ze zmian wymagań .
Jest to tylko pośrednio związane z stanem enkapsulacji, o czym, jak sądzę, myśli David Arno. Ta rada nie zawsze (ale często) sugeruje stan kapsułkowania, a ta rada dotyczy również obiektów niezmiennych. W rzeczywistości zwykłe nazywanie stałych jest (bardzo podstawową) formą enkapsulacji tego, co różni.
CandiedOrange wyraźnie łączy „to, co się zmienia” z „szczegółami”. Jest to tylko częściowo poprawne. Zgadzam się, że każdy kod, który się zmienia, jest w pewnym sensie „szczegółami”, ale „szczegół” może się nie różnić (chyba że zdefiniujesz „szczegóły”, aby uczynić to tautologicznym). Mogą istnieć powody, by ujmować nie zmieniające się szczegóły, ale to powiedzenie nie jest jedno. Z grubsza mówiąc, jeśli jesteś bardzo pewny, że „pies”, „kot” i „kaczka” będą jedynymi typami, z którymi kiedykolwiek będziesz musiał sobie poradzić, to to powiedzenie nie sugeruje refaktoryzacji, jaką wykonuje CandiedOrange.
Rzucając przykład CandiedOrange w innym kontekście, załóżmy, że mamy język proceduralny, taki jak C. Jeśli mam jakiś kod, który zawiera:
Mogę się spodziewać, że ten fragment kodu zmieni się w przyszłości. Mogę to „zamknąć” po prostu, definiując nową procedurę:
i stosując tę nową procedurę zamiast bloku kodu (tj. refaktoryzację metodą „wyodrębniania”). W tym momencie dodanie typu „krowa” lub cokolwiek innego wymaga jedynie zaktualizowania
speak
procedury. Oczywiście w języku OO możesz zamiast tego skorzystać z dynamicznej wysyłki, o czym wspomina odpowiedź CandiedOrange. Stanie się to naturalnie, jeśli uzyskasz dostęppet
przez interfejs. Eliminacja logiki warunkowej za pomocą dynamicznej wysyłki jest kwestią ortogonalną, która była częścią tego, dlaczego dokonałem tego proceduralnego wykonania. Chcę również podkreślić, że nie wymaga to cech charakterystycznych dla OOP. Nawet w języku OO hermetyzacja tego, co się różni, niekoniecznie oznacza, że należy utworzyć nową klasę lub interfejs.Jako bardziej archetypowy przykład (który jest bliższy, ale nie do końca OO), powiedzmy, że chcemy usunąć duplikaty z listy. Załóżmy, że wdrażamy to, iterując listę, śledząc przedmioty, które widzieliśmy do tej pory na innej liście, i usuwając wszystkie, które widzieliśmy. Rozsądnie jest założyć, że możemy chcieć zmienić sposób śledzenia obserwowanych przedmiotów, przynajmniej z powodów związanych z wydajnością. Wymóg zawarcia tego, co się różni, sugeruje, że powinniśmy zbudować abstrakcyjny typ danych, który reprezentowałby zestaw widocznych elementów. Nasz algorytm jest teraz zdefiniowany w oparciu o ten abstrakcyjny zestaw danych, a jeśli zdecydujemy się przejść na drzewo wyszukiwania binarnego, nasz algorytm nie musi się zmieniać ani się tym przejmować. W języku OO możemy użyć klasy lub interfejsu do przechwycenia tego abstrakcyjnego typu danych. W języku takim jak SML / O ”
W przykładzie opartym na wymaganiach powiedz, że musisz zweryfikować niektóre pola w odniesieniu do logiki biznesowej. Chociaż możesz mieć teraz określone wymagania, mocno podejrzewasz, że będą ewoluować. Możesz zawrzeć aktualną logikę we własnej procedurze / funkcji / regule / klasie.
Chociaż jest to problem ortogonalny, który nie jest częścią „enkapsulacji tego, co się różni”, często naturalne jest wyodrębnienie, które jest sparametryzowane przez teraz enkapsulowaną logikę. Zwykle prowadzi to do bardziej elastycznego kodu i pozwala na zmianę logiki poprzez zastąpienie jej alternatywną implementacją zamiast modyfikowania logiki zamkniętej.
źródło
„Hermetyzuj to, co się zmienia” odnosi się do ukrywania szczegółów implementacji, które mogą się zmieniać i ewoluować.
Przykład:
Załóżmy na przykład, że klasa
Course
śledzi,Students
czy można zarejestrować (). Możesz go zaimplementować za pomocą aLinkedList
i odsłonić kontener, aby umożliwić iterację na nim:Ale to nie jest dobry pomysł:
Jeśli hermetyzujesz to, co się różni (a raczej powiedział, co może się różnić), zachowujesz swobodę zarówno przy użyciu kodu, jak i enkapsulowanej klasy, aby samodzielnie ewoluowały. Dlatego jest to ważna zasada w OOP.
Dodatkowe czytanie:
źródło