Więc chciałem odziedziczyć sealed class
po csharp i zostałem spalony. Po prostu nie ma możliwości jej odblokowania, chyba że masz dostęp do źródła.
Wtedy pomyślałem sobie „dlaczego w sealed
ogóle istnieje?”. 4 miesiące temu. Nie potrafiłem tego rozgryźć, mimo że czytałem o nim wiele rzeczy, takich jak:
- Jon Skeet życzy sobie „ zajęcia były domyślnie zapieczętowane w .NET ”.
- Wolisz kompozycję niż dziedziczenie?
- „Nie należy zapieczętowywać wszystkich klas (...)”
- Jak kpisz z zapieczętowanej klasy?
Od tego czasu próbowałem to wszystko przetrawić, ale dla mnie to po prostu za dużo. W końcu wczoraj spróbowałem jeszcze raz. Ponownie przejrzałem je wszystkie oraz kilka innych:
- Dlaczego klasa powinna być czymś innym niż „abstrakcyjnym” lub „ostatecznym / zapieczętowanym”?
- W ciągu ponad 15 lat programowania, po raz pierwszy usłyszałem o SOLID , w odpowiedzi na pytanie już powiązane i oczywiście nie przeczytałem go wszystkie 4 miesiące temu
W końcu, po wielu zastanawiać, zdecydowałem się mocno zmieniać oryginalnego pytanie w oparciu o nowy tytuł.
Stare pytanie było zbyt szerokie i subiektywne. Zasadniczo pytano:
- W starym tytule: Jeden dobry powód, aby używać zapieczętowanego
- W ciele: jak poprawnie zmodyfikować zapieczętowaną klasę? Zapomniałeś o spadku? Używać kompozycji?
Ale teraz, rozumiejąc (czego nie wczoraj), że wszystko sealed
robi, zapobiega dziedziczeniu , a my możemy i powinniśmy używać kompozycji zamiast dziedziczenia , zdałem sobie sprawę, że potrzebowałem praktycznych przykładów.
Wydaje mi się, że moje pytanie jest (a właściwie zawsze było) dokładnie tym, co zaproponował mi pan na czacie : Jak projektowanie dziedziczenia może powodować dodatkowe koszty?
źródło
Odpowiedzi:
Nie jest to takie trudne do zrozumienia.
sealed
został stworzony specjalnie dla Microsoft, aby ułatwić im życie, zaoszczędzić mnóstwo pieniędzy i poprawić reputację. Ponieważ jest to funkcja językowa, wszyscy inni również mogą z niej korzystać, ale prawdopodobnie nigdy nie będziesz musiał używać zapieczętowanego.Większość ludzi narzeka, że nie jest w stanie przedłużyć klasy, a jeśli tak, to dobrze mówi, że wszyscy wiedzą, że to na programistach spoczywa obowiązek poprawnego działania. Zgadza się, Z WYJĄTKIEM tych samych osób, które nie mają pojęcia o historii systemu Windows, a jednym z problemów
sealed
jest próba rozwiązania.Załóżmy, że programista rozszerzył podstawową klasę .Net (ponieważ sealed nie istniał) i sprawił, że działał idealnie. Tak, dla programisty. Deweloper dostarcza produkt do klienta. Aplikacja programisty działa świetnie. Klient jest zadowolony. Życie jest dobre.
Teraz Microsoft wypuszcza nowy system operacyjny, który obejmuje naprawianie błędów w tej konkretnej klasie podstawowej .Net. Klient chce nadążać za czasem i decyduje się na instalację nowego systemu operacyjnego. Nagle aplikacja, którą tak bardzo lubi klient, przestała działać, ponieważ nie uwzględniła błędów, które zostały naprawione w podstawowej klasie .Net.
Kto ponosi winę?
Osoby zaznajomione z historią Microsoftu wiedzą, że nowy system operacyjny Microsoftu otrzyma winę, a nie oprogramowanie, które niewłaściwie wykorzystywało biblioteki Windows. Następnie Microsoft musi rozwiązać problem, a nie firma, która go stworzyła. Jest to jeden z powodów, dla których kod Windows został rozdęty. Z tego, co przeczytałem, kod systemu operacyjnego Windows jest wypełniony konkretnym if-następnie sprawdza, czy konkretna aplikacja i wersja jest uruchomiona, a jeśli tak, to wykonaj specjalne przetwarzanie, aby umożliwić działanie programu. To dużo pieniędzy wydanych przez Microsoft na naprawę niekompetencji innej firmy.
Używanie
sealed
nie eliminuje całkowicie powyższego scenariusza, ale pomaga.źródło
sealed
domyślnie, o ile nie były specjalnie zapieczętowane, po prostu dlatego, że tak niewiele klas zostało zaprojektowanych do dziedziczenia. O wiele bardziej prawdopodobne jest, że twoja klasa nie została zbudowana, aby ją wspierać, i powinieneś upewnić się, że spędziłeś ten czas deweloperów, jeśli robisz wszystko, aby oznaczyć go jako niezamknięty.sealed
. Gdyby w rzeczywistości była to opcja domyślna, oznaczałoby to, że jeśli ktoś dziedziczy po typie, wiedziałby, że jest on przystosowany do jego obsługi.String
a następnie zmutuje ją po sprawdzeniu bezpieczeństwa, ale przed We / Wy? Uważam, że ten rodzaj scenariusza jest powodem, dla którego JavaString
jest oznaczonafinal
(podobnie dosealed
) i założę się, że ta sama logika stanowi podstawę niektórych decyzji w języku C #.sealed
służy do wskazania, że autor klasy nie zaprojektował go do dziedziczenia. Nic mniej, nic więcej.Prawidłowe zaprojektowanie interfejsu jest już wystarczająco trudne dla wielu programistów. Właściwe zaprojektowanie klasy, która może być potencjalnie odziedziczona, zwiększa trudność i linie kodu. Więcej wierszy napisanego kodu oznacza więcej kodu do utrzymania. Aby uniknąć tej rosnącej trudności,
sealed
można wykazać, że klasa nigdy nie była przeznaczona do dziedziczenia, aw tym kontekście uszczelnienie klasy jest idealnie poprawnym rozwiązaniem problemu .Wyobraźmy sobie sytuację, w której klasa
CustomBoeing787
pochodzi odAirliner
. ToCustomBoeing787
nadpisuje niektóre metody z ochronąAirliner
, ale nie wszystkie z nich. Jeśli programista ma wystarczająco dużo czasu i warto, może przetestować klasę, aby upewnić się, że wszystko działa zgodnie z oczekiwaniami, nawet w kontekście, gdy wywoływane są metody nie nadpisywane (na przykład chronione obiekty ustawiające, które zmieniają stan obiektu). Jeśli ten sam programista (1) nie ma czasu, (2) nie chce wydać dodatkowych setek lub tysięcy dolarów swojego szefa / klienta, lub (3) nie chce pisać dodatkowego kodu, nie chce potrzebuje teraz (YAGNI), a następnie może:albo wypuść kod na dziko, biorąc pod uwagę, że to programiści będą utrzymywać ten projekt później, aby dbać o potencjalne błędy,
lub oznaczyć klasę,
sealed
aby wyraźnie pokazała przyszłym programistom, że klasa ta nie jest przeznaczona do dziedziczenia, i że jej rozszczelnienie i użycie jej jako elementu nadrzędnego powinno zostać wykonane na własne ryzyko.Czy dobrym pomysłem jest opieczętowanie jak największej liczby klas, czy jak najmniej? Z mojego doświadczenia wynika, że nie ma ostatecznej odpowiedzi. Niektórzy programiści używają
sealed
zbyt wiele; inni nie dbają o to, jak klasa zostanie odziedziczona i jaki może powodować problem. Biorąc pod uwagę takie użycie, problem jest podobny do tego, który polega na określeniu, czy programiści powinni użyć dwóch lub czterech spacji do wcięcia: nie ma właściwej odpowiedzi.źródło
tl;dr
tutaj. To albo „Nie, nie ma jednego dobrego powodu” , albo „Myślę, że nie ma żadnego dobrego powodu, ale ja też nie wiem”. . Dla mnie „żadna ostateczna odpowiedź” nie jest jedną z nich.sealed
służy do wskazania, że autor klasy nie zaprojektował go do dziedziczenia. To twój jedyny dobry powód.String
a następnie zmutuje ją po sprawdzeniu bezpieczeństwa, ale przed We / Wy? Uważam, że ten rodzaj scenariusza jest powodem, dla którego JavaString
jest oznaczonafinal
(podobnie dosealed
) i założę się, że ta sama logika stanowi podstawę niektórych decyzji w języku C #.Gdy klasa jest zapieczętowana, kod używający tego typu może dokładnie wiedzieć, z czym ma do czynienia.
Teraz w C #
string
jestsealed
, nie można dziedziczyć po nim, ale załóżmy na chwilę, że to nie był przypadek. Jeśli tak, ktoś mógłby napisać taką klasę:To byłaby teraz klasa strun, która narusza wszelkiego rodzaju właściwości, o których projektanci
string
wiedzieli, że są prawdziwe. Wiedzieli, żeEquals
metoda będzie przechodnia, tak nie jest. Heck, ta metoda równości nie jest nawet symetryczna, ponieważ łańcuch może być równy, ale nie byłby równy temu łańcuchowi. Jestem pewien, że są różnego rodzaju programiści, którzy napisali kod przy założeniu, żeToString
metodastring
nie zgłasza wyjątku dla wartości niepustych. Będziesz mógł teraz naruszyć to założenie.Istnieje wiele innych klas, dla których moglibyśmy to zrobić, i wszelkiego rodzaju inne założenia, które moglibyśmy złamać. Jeśli usuniesz funkcję języka dla
sealed
projektanci języków muszą teraz zacząć rozliczać takie rzeczy w całym kodzie biblioteki. Muszą wykonywać wszelkiego rodzaju kontrole, aby upewnić się, że rozszerzone typy typów, których chcą używać, zostaną zapisane „poprawnie”, co może być bardzo kosztowne (zarówno w czasie projektowania, jak i w czasie wykonywania). Będąc w stanie zaplombować klasę, mogą zagwarantować, że nie jest to możliwe, i że nie można naruszyć jakichkolwiek zapewnień dotyczących szczegółów implementacji ich własnych typów, z wyjątkiem tych typów, które zostały celowo zaprojektowane do rozszerzenia. W przypadku tych typów, które mają zostać rozszerzone, przekonasz się, że kod języka musi być znacznie bardziej defensywny w zakresie tego, jak sobie z nimi radzi.źródło
sealed
wbudowanych funkcji, które nie są dla nas dostępne. Nadal nie wyjaśnia, dlaczego używać go poza tak wąskim zakresem, a będąc tak wąskim jak kod kompilatora , nawet nie wyjaśniasealed
istnienia.string
Typ nie jest kodem kompilatora. Jest to część kodu języka. Zupełnie inny. W każdym razie wybrałem tylko jeden przykład. Możesz odebrać większość zapieczętowanych klas w całym BCL i użyć tego jako przykładu.Enh? Nie często. Będę stosować tylko zamknięte, kiedy mam typ niezmienna (lub jakieś inne ograniczenia), że naprawdę naprawdę musi pozostać niezmienna i nie można ufać spadkobiercami do furfill ten wymóg nie wprowadzając subtelne, catastropic błędów w systemie.
Używamy dziedziczenia dla rzeczy, które nie są domyślne. Nawet jeśli kompozycja preferuje dziedziczenie (i tak jest), nie oznacza to, że dziedziczenie należy odrzucić. Oznacza to, że dziedziczenie ma pewne nieodłączne problemy, które wprowadza w projektowaniu i utrzymywaniu kodu, a kompozycja robi to samo z (ogólnie) mniejszymi problemami. A to oznacza, że nawet jeśli skład jest faworyzowany że Dziedziczenie jest dobre dla niektórych podgrupach problemami.
Nie chcę być zbyt wredny, ale jeśli właśnie słyszałeś o SOLID i testowaniu jednostkowym, być może powinieneś wyjść spod skały i napisać kod. Sprawy nie są jednoznaczne i rzadko „zawsze robię X”, „nigdy nie używam Y” lub „Z najlepiej” jest właściwą drogą (jakby wydaje się, że skłaniasz się ku podstawie opinii). Podczas pisania kodu możesz zobaczyć przypadki, w których
sealed
może to być dobre, oraz przypadki, w których powoduje to smutek - tak jak każdy inny kompromis.źródło
sealed
, proszę?Przede wszystkim powinieneś przeczytać post Erica Lipperta o tym, dlaczego Microsoft uszczelnia swoje klasy.
http://blogs.msdn.com/b/ericlippert/archive/2004/01/22/61803.aspx
Po drugie, zapieczętowane słowo kluczowe istnieje, aby robić dokładnie to, co robi - zapobiegać dziedziczeniu. Istnieje wiele sytuacji, w których chcesz zapobiec dziedziczeniu. Zastanów się, jaką klasę masz. Jeśli Twój kod zależy od działania tej klasy w określony sposób, nie chcesz, aby ktoś przesłaniał jedną z metod lub właściwości w tej klasie i spowodował awarię aplikacji.
Po trzecie, stosowanie zapieczętowanego zachęca do tworzenia kompozycji zamiast dziedziczenia, co jest dobrym nawykiem. Używanie kompozycji zamiast dziedziczenia oznacza, że nie możesz złamać zasady podstawienia Liskowa i postępujesz zgodnie z zasadą Otwarta / Zamknięta.
sealed
jest zatem słowem kluczowym, które pomaga w przestrzeganiu dwóch z pięciu najważniejszych zasad programowania oo. Powiedziałbym, że dzięki temu jest to bardzo cenna funkcja.źródło
Myślę, że na to pytanie nie ma obiektywnej odpowiedzi i że wszystko zależy od twojej perspektywy.
Z jednej strony, niektórzy ludzie chcą, aby wszystko było „otwarte”, a ciężar zapewnienia, że wszystko działa poprawnie, spoczywa na osobie, która rozszerza klasę. Właśnie dlatego klasy są domyślnie otwarte na dziedziczenie. I dlaczego wszystkie metody Java są domyślnie wirtualne.
Z drugiej strony, niektórzy chcą, aby wszystko było domyślnie zamknięte i zawierało opcje, aby je otworzyć. W tym przypadku autor klasy podstawowej musi upewnić się, że wszystko, co czyni „otwartym”, jest bezpieczne w użyciu w dowolny możliwy sposób. Dlatego w języku C # wszystkie metody są domyślnie nie-wirtualne i muszą zostać uznane za wirtualne, aby zostać zastąpione. To także dla tych ludzi, którzy muszą być sposobem na obejście „otwartych” domyślnych ustawień. Jak robienie zamkniętych / końcowych zajęć, ponieważ widzą, że ktoś wywodzący się z klasy stanowi duży problem w projektowaniu ich własnej klasy.
źródło
sealed
żargon. Jak możemy je odziedziczyć? Czytam tylko, że nie możemy. Zawsze. Nie wiem Jestem w tej sprawie od 4 miesięcy, odkąd po raz pierwszy usłyszałem o zapieczętowanym. I nadal nie wiem, jak to zrobić dobrze, kiedy trzeba nic przerabiać na zamkniętej klasie. Nie widzę więc „opcji, aby ją otworzyć”!Problem z zapieczętowanym w C # polega na tym, że jest on raczej ostateczny. W ciągu 7 lat komercyjnego rozwoju C # jedyne miejsce, w którym widziałem, że jest używane, to klasy Microsoft .NET, w których uważam, że miałem uzasadniony powód, aby rozszerzyć klasę ramową w celu wdrożenia dostosowanego zachowania, a ponieważ klasa została zapieczętowana, nie mogłem .
Niemożliwe jest napisanie klasy rozważającej każdy możliwy sposób jej wykorzystania i stwierdzającej, że żadna z nich nie jest zgodna z prawem. Podobnie, jeśli bezpieczeństwo frameworka zależy od tego, czy klasa nie jest dziedziczona, masz wadę w projektowaniu zabezpieczeń.
Podsumowując, jestem zdecydowanie zdania, że zapieczętowany nie służy żadnemu pożytecznemu celowi i nic, co do tej pory czytałem, nie zmieniło tego poglądu.
Widzę, że do tej pory istniały trzy argumenty, które uzasadniają umieszczenie zapieczętowanego.
Argument 1 jest taki, że eliminuje on źródło błędów, gdy ludzie dziedziczą po klasach, a następnie mają większy dostęp do elementów wewnętrznych tej klasy, bez właściwego zrozumienia elementów wewnętrznych.
Argument 2 jest taki, że jeśli rozszerzysz klasę Microsoft, a następnie wdrożysz poprawkę błędu w tej klasie, ale twoje rozszerzenie polegało na tym błędzie, twój kod jest teraz zepsuty, Microsoft dostaje winę, a zapieczętowane zapobiega
Argument 3 jest taki, że jako twórca klasy, moim wyborem jest zezwolenie na jej rozszerzenie w ramach mojego projektu klasy.
Argument 1 wydaje się argumentem, że ty programista końcowy nie wiesz, jak używać mojego kodu. Może tak być, ale jeśli nadal pozwalasz mi uzyskać dostęp do interfejsu obiektów, nadal jest prawdą, że używam twojego obiektu i wykonuję do niego wywołania, nie rozumiejąc, jak go używać, a więc może w ten sposób wyrządzić tyle samo szkód . Jest to słabe uzasadnienie potrzeby zapieczętowania.
Rozumiem, skąd pochodzi faktyczna podstawa dla arg 2. W szczególności, gdy Microsoft zastosował dodatkowe kontrole systemu operacyjnego w wywołaniach interfejsu API win32, aby zidentyfikować zniekształcone wywołania, które poprawiły ogólną stabilność systemu Windows XP w porównaniu z Windows NT, ale w krótkim okresie czasu wiele aplikacji zostało uszkodzonych po wydaniu systemu Windows XP. Microsoft rozwiązał ten problem, wprowadzając „reguły”, aby powiedzieć, że te kontrole mówią, ignoruj, gdy aplikacja X złamie tę regułę, jest to znany błąd
Argument 2 jest kiepskim argumentem, ponieważ sealed w najlepszym razie nie robi żadnej różnicy w tym, ponieważ jeśli jest błąd w bibliotece .NET, nie masz wyboru, jak znaleźć obejście, a kiedy pojawi się poprawka Microsoft, jest całkiem prawdopodobne oznacza, że twoja praca, która zależy od złamanego zachowania, jest teraz zepsuta. Bez względu na to, czy obejdziesz ten problem przy użyciu dziedziczenia, czy innego dodatkowego kodu opakowania, po naprawieniu kodu Twoja obejście zostanie przerwane.
Argument 3 jest jedynym argumentem, który ma jakąkolwiek substancję uzasadniającą siebie. Ale wciąż jest słaby. Jest to po prostu sprzeczne z wielokrotnym użyciem kodu.
źródło
It is impossible to write a class thinking about every possible way it could be used
- Właśnie dlatego wiele klas Framework jest zamkniętych ... Eliminuje to potrzebę myślenia o każdym możliwym sposobie, w jaki ktoś może rozszerzyć klasę.Używam
sealed
słowa kluczowego tylko w klasach, które zawierają tylko metody statyczne.źródło
static
?Jak wynika z mojego pytania, nie jestem tutaj ekspertem. Ale nie widzę powodu,
sealed
by istnieć.Wydaje się, że ma to pomóc architektom kodu w zaprojektowaniu interfejsu. Jeśli chcesz napisać klasę, która wyjdzie na wolność, a wiele osób odziedziczy po niej, modyfikowanie czegokolwiek w tej klasie może przerwać ją dla zbyt wielu osób. Więc możemy to zapieczętować i nikt nie może odziedziczyć. "Problem rozwiązany".
Cóż, wygląda jak „zakrycie jednego miejsca i odkrycie innego”. I nawet nie rozwiązuje problemu - po prostu eliminuje narzędzie : dziedziczenie. Jak każde narzędzie ma wiele zastosowań, a dziedziczenie po niestabilnej klasie stanowi ryzyko. Ktokolwiek podjął to ryzyko, powinien wiedzieć lepiej.
Może może istnieć słowo kluczowe
stable
, aby ludzie wiedzieli, że bezpieczniej jest po nim dziedziczyć.źródło
sealed
nie jest to przydatne narzędzie dla projektantów ram. Klasy w ramach powinny być czarnymi skrzynkami; nie powinieneś wiedzieć o wewnętrznych elementach klasy, aby móc z niej poprawnie korzystać. Jednak dokładnie tego wymaga dziedziczenie.