Zastanawiałem się nad tym, kiedy używać prywatnie, a kiedy chronione w klasach. (Rozszerzę to pytanie również na końcowe klasy i metody, ponieważ jest ono powiązane. Programuję w Javie, ale myślę, że dotyczy to każdego języka OOP)
Dobrą zasadą jest: uczyń wszystko tak prywatnym, jak to możliwe.
- Spraw, by wszystkie klasy były ostateczne, chyba że musisz je natychmiast podzielić na klasy.
- Spraw, by wszystkie metody były ostateczne, chyba że musisz od razu je podklasować i zastąpić.
- Spraw, aby wszystkie parametry metody były ostateczne, chyba że musisz je zmienić w ciele metody, co w większości przypadków jest dość niewygodne.
Jest to dość proste i jasne, ale co jeśli głównie piszę biblioteki (Open Source na GitHub) zamiast aplikacji?
Mógłbym wymienić wiele bibliotek i sytuacji, w których
- Biblioteka została rozbudowana w sposób, jakiego twórcy nigdy nie pomyśleli
- Musiało to zostać zrobione z „magią ładującą klasy” i innymi hackami z powodu ograniczeń widoczności
- Biblioteki wykorzystano w sposób, w jaki nie zostały zbudowane, a potrzebną funkcjonalność „włamano się”
- Nie można użyć bibliotek z powodu małego problemu (błąd, brak funkcjonalności, „niewłaściwe” zachowanie), którego nie można zmienić z powodu ograniczonej widoczności
- Problem, którego nie można naprawić, doprowadził do ogromnych, brzydkich i błędnych obejść, w których zastąpienie prostej funkcji (prywatnej lub ostatecznej) mogło pomóc
Zacząłem je nazywać, dopóki pytanie nie stało się zbyt długie, i postanowiłem je usunąć.
Podoba mi się pomysł, aby nie mieć więcej kodu niż potrzeba, więcej widoczności niż potrzeba, więcej abstrakcji niż potrzeba. Może to działać podczas pisania aplikacji dla użytkownika końcowego, gdzie kod jest używany tylko przez tych, którzy go piszą. Ale jak to się utrzymuje, jeśli kod ma być używany przez innych programistów, gdzie jest nieprawdopodobne, aby pierwotny programista z góry pomyślał o każdym możliwym przypadku użycia, a zmiany / refaktory są trudne / niemożliwe do wprowadzenia?
Ponieważ duże biblioteki open source nie są niczym nowym, jaki jest najczęstszy sposób obsługi widoczności w takich projektach z językami obiektowymi?
Odpowiedzi:
Niefortunna prawda jest taka, że wiele bibliotek jest pisanych , a nie projektowanych . To smutne, ponieważ odrobina wcześniejszych przemyśleń może zapobiec wielu problemom na drodze.
Jeśli postanowimy zaprojektować bibliotekę, pojawi się pewien zestaw przewidywanych przypadków użycia. Biblioteka może nie spełniać bezpośrednio wszystkich przypadków użycia, ale może służyć jako część rozwiązania. Dlatego biblioteka musi być wystarczająco elastyczna, aby się przystosować.
Ograniczeniem jest to, że zazwyczaj nie jest dobrym pomysłem pobranie kodu źródłowego biblioteki i zmodyfikowanie go w celu obsługi nowego przypadku użycia. W przypadku bibliotek zastrzeżonych źródło może nie być dostępne, aw przypadku bibliotek typu open source może być niepożądane utrzymanie wersji rozwidlonej. Połączenie wysoce specyficznych dostosowań w projekcie wyższego szczebla może być niewykonalne.
Tutaj pojawia się zasada otwartego zamknięcia: biblioteka powinna być otwarta na rozszerzenie bez modyfikowania kodu źródłowego. To nie przychodzi naturalnie. To musi być celowy cel projektu. Istnieje wiele technik, które mogą tu pomóc, klasyczne wzorce projektowania OOP to tylko niektóre z nich. Ogólnie rzecz biorąc, określamy zaczepy, w których kod użytkownika może bezpiecznie podłączyć się do biblioteki i dodać funkcjonalność.
Po prostu upublicznienie każdej metody lub zezwolenie na podklasę każdej klasy nie jest wystarczające do osiągnięcia rozszerzalności. Po pierwsze, rozszerzenie biblioteki jest naprawdę trudne, jeśli nie jest jasne, gdzie użytkownik może podłączyć się do biblioteki. Np. Przesłonięcie większości metod nie jest bezpieczne, ponieważ metoda klasy bazowej została napisana z niejawnymi założeniami. Naprawdę musisz zaprojektować dla rozszerzenia.
Co ważniejsze, gdy coś jest częścią publicznego interfejsu API, nie można go odzyskać. Nie można go refaktoryzować bez zerwania kodu dalszego przetwarzania. Przedwczesna otwartość ogranicza bibliotekę do nieoptymalnego projektu. W przeciwieństwie do tego, uczynienie wewnętrznych rzeczy prywatnymi, ale dodawanie haków, jeśli później będą potrzebne, jest bezpieczniejszym podejściem. Mimo, że jest to sposób rozsądny zająć się ewolucją długoterminową biblioteki, jest niezadowalająca dla użytkowników, którzy chcą korzystać z biblioteki teraz .
Co zatem się dzieje? Jeśli obecny stan biblioteki powoduje znaczny ból, programiści mogą wziąć całą wiedzę na temat faktycznych przypadków użycia, które nagromadziły się w czasie, i napisać wersję 2 biblioteki. Będzie świetnie! Naprawi to wszystkie błędy związane z projektowaniem! Zajmie to również więcej czasu, niż się spodziewano, w wielu przypadkach zgasło. A jeśli nowa wersja jest bardzo odmienna od starej, może być trudno zachęcić użytkowników do migracji. Pozostajesz wtedy utrzymywać dwie niekompatybilne wersje.
źródło
Każda publiczna i rozszerzalna klasa / metoda jest częścią interfejsu API, który musi być obsługiwany. Ograniczenie tego zestawu do rozsądnego podzbioru biblioteki zapewnia największą stabilność i ogranicza liczbę rzeczy, które mogą się nie udać. Jest to decyzja zarządcza (a nawet projekty OSS są zarządzane w pewnym stopniu) na podstawie tego, co możesz w uzasadniony sposób wesprzeć.
Różnica między OSS a zamkniętym źródłem polega na tym, że większość ludzi próbuje stworzyć i rozwijać społeczność wokół kodu, tak aby więcej niż jedna osoba obsługiwała bibliotekę. To powiedziawszy, dostępnych jest wiele narzędzi do zarządzania:
W dojrzałych projektach zobaczysz coś takiego:
W tym momencie, jeśli zmiana została zaakceptowana, ale użytkownik chce przyspieszyć jej naprawę, może wykonać pracę i przesłać żądanie ściągnięcia lub łatkę (w zależności od narzędzia kontroli wersji).
Żadne API nie jest statyczne. Jednak jego rozwój musi być w jakiś sposób ukształtowany. Trzymając wszystko zamknięte, dopóki nie zostanie wykazana potrzeba otwarcia rzeczy, unikniesz reputacji błędnej lub niestabilnej biblioteki.
źródło
Przeredaguję moją odpowiedź, ponieważ wydaje się, że uderzyła ona w nerwy kilku osobom.
widoczność właściwości / metody klasy nie ma nic wspólnego z bezpieczeństwem ani otwartością źródła.
Powodem istnienia widoczności jest to, że obiekty są wrażliwe na 4 konkretne problemy:
Jeśli zbudujesz moduł nieskapsułowany, użytkownicy przyzwyczają się do bezpośredniej zmiany stanu modułu. Działa to dobrze w środowisku z jednym wątkiem, ale kiedy nawet pomyślisz o dodaniu wątków; będziesz zmuszony uczynić państwo prywatnym i używać blokad / monitorów wraz z modułami pobierającymi i ustawiającymi, które zmuszają inne wątki do oczekiwania na zasoby, zamiast ścigać się na nich. Oznacza to, że programy użytkowników nie będą już działać, ponieważ do zmiennych prywatnych nie można uzyskać dostępu w tradycyjny sposób. Może to oznaczać, że potrzebujesz dużo przepisywania.
Prawda jest taka, że o wiele łatwiej jest kodować przy użyciu jednego wątkowego środowiska uruchomieniowego, a prywatne słowo kluczowe pozwala po prostu dodać słowo kluczowe zsynchronizowane lub kilka blokad, a kod użytkowników nie złamie się, jeśli zostanie zamknięty w sobie od początku .
Każdy obiekt ma wiele rzeczy, które muszą być prawdziwe, aby być w stanie spójnym. Niestety, te rzeczy żyją w widocznej przestrzeni klienta, ponieważ przenoszenie każdego obiektu do własnego procesu i rozmawianie z nim za pomocą wiadomości jest drogie. Oznacza to, że obiektowi bardzo łatwo jest zawiesić cały program, jeśli użytkownik ma pełną widoczność.
Jest to nieuniknione, ale można zapobiec przypadkowemu wprowadzeniu obiektu w niespójny stan, zamykając interfejs w jego usługach, co zapobiega przypadkowym awariom, pozwalając tylko użytkownikowi na interakcję ze stanem obiektu poprzez starannie spreparowany interfejs, który czyni program o wiele bardziej niezawodnym . Nie oznacza to, że użytkownik nie może celowo uszkodzić niezmienników, ale jeśli tak, to ich klient ulega awarii, wystarczy, że uruchomi ponownie program (dane, które chcesz chronić, nie powinny być przechowywane po stronie klienta) ).
Innym dobrym przykładem, w którym można poprawić użyteczność modułów, jest ustawienie konstruktora na prywatny; ponieważ jeśli konstruktor zgłosi wyjątek, zabije program. Jednym leniwym podejściem do rozwiązania tego problemu jest spowodowanie przez konstruktora błędu czasu kompilacji, którego nie można zbudować, chyba że znajduje się on w bloku try / catch. Ustawiając konstruktor na prywatny i dodając publiczną statyczną metodę tworzenia, można ustawić, aby metoda tworzenia zwróciła null, jeśli nie uda się jej zbudować, lub weź funkcję wywołania zwrotnego do obsługi błędu, dzięki czemu program będzie bardziej przyjazny dla użytkownika.
Wiele klas ma wiele stanów i metod i łatwo jest zostać przytłoczonym, próbując je przewijać; Wiele z tych metod to po prostu szum wizualny, taki jak funkcje pomocnicze, stan. ustawienie zmiennych i metod na prywatne pomaga zmniejszyć zanieczyszczenie zakresem i ułatwia użytkownikowi znalezienie usług, których szukają.
Zasadniczo pozwala ci uniknąć funkcji pomocniczych wewnątrz klasy, a nie poza nią; bez kontroli widoczności bez rozpraszania użytkownika szeregiem usług, z których użytkownik nigdy nie powinien korzystać, dzięki czemu można uniknąć podziału metod na kilka metod pomocniczych (choć nadal będzie to zanieczyszczało zakres, ale nie użytkownika).
Dobrze spreparowany interfejs może ukryć wewnętrzne bazy danych / okna / obrazowanie, od których zależy jego wykonanie, a jeśli chcesz przejść do innej bazy danych / innego systemu okien / innej biblioteki obrazowania, możesz zachować ten sam interfejs i użytkowników nie zauważy.
Z drugiej strony, jeśli tego nie zrobisz, możesz łatwo wpaść w niemożność zmiany swoich zależności, ponieważ są one ujawnione, a kod na tym polega. Przy wystarczająco dużym systemie koszt migracji może stać się nieosiągalny, a jego enkapsulacja może chronić dobrze zachowujących się użytkowników klientów przed przyszłymi decyzjami o zamianie zależności.
źródło