W jaki sposób projektowanie dziedziczenia może powodować dodatkowe koszty? [Zamknięte]

12

Więc chciałem odziedziczyć sealed classpo 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 sealedogóle istnieje?”. 4 miesiące temu. Nie potrafiłem tego rozgryźć, mimo że czytałem o nim wiele rzeczy, takich jak:

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:

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:

  1. W starym tytule: Jeden dobry powód, aby używać zapieczętowanego
  2. W ciele: jak poprawnie zmodyfikować zapieczętowaną klasę? Zapomniałeś o spadku? Używać kompozycji?

Ale teraz, rozumiejąc (czego nie wczoraj), że wszystko sealedrobi, 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?

Cregox
źródło
5
Pytanie jest sformułowane dość agresywnie i brzmi jak rant. Powinieneś przeredagować go, jeśli chcesz trafnych i obiektywnych odpowiedzi.
Euforii
11
Zobacz, dlaczego tak wiele klas ramowych jest zapieczętowanych? Eric Lippert. Podaje kilka dobrych powodów, aby uszczelniać klasy.
Robert Harvey
9
Wtedy nie przeczytałeś tego artykułu. Wróć i przeczytaj to jeszcze raz. Sprowadza się to do tego: nie można przewidzieć niezliczonych sposobów, w jaki klient może złamać twoją klasę, rozszerzając ją. Możesz winić za to swoich klientów, ale to ty będziesz musiał ich wspierać.
Robert Harvey
4
@Cawas Mógł lub możesz przeczytać artykuł, który podlinkował, który szczegółowo to wyjaśnia. Właśnie podsumowywał ten artykuł swoim komentarzem.
Servy
7
Przepraszam, ale nie jestem tym, który tu gada. Odpowiedź na twoje pytanie jest bardzo prosta: zapisz klasę, gdy nie chcesz wspierać dziedziczenia. Otóż ​​to.
Robert Harvey

Odpowiedzi:

27

Nie jest to takie trudne do zrozumienia. sealedzostał 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 sealedjest 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 sealednie eliminuje całkowicie powyższego scenariusza, ale pomaga.

Maczać
źródło
4
@Cawas Bardzo trudno go nadużyć. Eric Lippert oświadczył przy wielu okazjach, że życzy sobie, aby wszystkie klasy, podobnie jak metody, były sealeddomyś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.
Servy
1
Myślę, że jeśli ludzie używają zamkniętego oprogramowania do wewnętrznego programowania, prawdopodobnie niewłaściwie go używają lub po prostu marnują czas firmy, próbując być zbyt doskonałym. Mówię zapewne dlatego, że zawsze są przypadki, w których można wymyślić taki sens w niektórych scenariuszach. Jednak dla osób opracowujących oprogramowanie typu bibliotecznego podejrzewam, że będzie ono bardzo przydatne z tych samych powodów, które Microsoft uznał za potrzebne.
Dunk
2
@Cawas Zdecydowana większość klas napisanych przez większość programistów nigdy nie będzie musiała być dziedziczona i nie są napisane w celu obsługi dziedziczenia. Dzięki temu typowi można to szybko i skutecznie przekazać czytelnikom / użytkownikom kodu 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.
Servy
2
Odblokowanie klasy nie oznaczałoby, że klasa została zbudowana do jej obsługi. Oznacza to po prostu, że ktoś chciał odziedziczyć klasę i ją otworzyć. W najlepszym przypadku użycie zapieczętowanego spowoduje po prostu, że programiści przepisają istniejącą funkcjonalność (równoważną klasie zapieczętowanej), ale częściej, jeśli kod źródłowy jest dostępny, po prostu go rozszczelnią, nie biorąc pod uwagę żadnych konsekwencji. Po ustawieniu zaplombowania w sposób szczególny i rzadko jest to lepsze, ponieważ wtedy deweloper może chcieć dowiedzieć się, dlaczego ta klasa jest zapieczętowana. Zbyt wiele zamkniętych klas i nie będzie ich obchodzić dlaczego.
Dunk
1
Również bezpieczeństwo jest powodem do korzystania z niego! Rozważ aplikację w trybie piaskownicy próbującą uzyskać dostęp do pliku. Piaskownica może przetestować ciągi nazw plików, ale co, jeśli osoba atakująca może wysłać ci zmienną, 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 Java Stringjest oznaczona final(podobnie do sealed) i założę się, że ta sama logika stanowi podstawę niektórych decyzji w języku C #.
Darien
23

sealedsł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, sealedmoż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 CustomBoeing787pochodzi od Airliner. To CustomBoeing787nadpisuje 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ę, sealedaby 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ą sealedzbyt 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.

Arseni Mourzenko
źródło
W porządku ... Przepraszam, jeśli znów zabrzmię agresywnie, ale chcę tu być jasny i asertywny ... Mogę zrozumieć tylko to, co właśnie napisałeś na dwa sposoby, jeśli napisałeś tl;drtutaj. 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.
cregox
6
sealedsłuży do wskazania, że ​​autor klasy nie zaprojektował go do dziedziczenia. To twój jedyny dobry powód.
Robert Harvey
To dobry powód, aby dać ci rower bez świateł i egzekwować, w jakiś sposób nie możesz dodawać świateł.
cregox
2
@Cawas Jak to zrobić? W tym kontekście konstruktorowi roweru nie jest ważne, aby upewnić się, że rower nie ma światła, ale często ważne jest, aby użytkownik typu upewnił się, że implementacja jest konkretną dokładną implementacją. Egzekwujesz ograniczenia, których potrzebujesz. W niektórych przypadkach ludzie mogą dodać ograniczenie, które w rzeczywistości nie jest potrzebne; podałeś przykład tego. To, że ograniczenie jest niewłaściwie użyte w jednym miejscu, nie oznacza, że ​​nigdy nie powinieneś ograniczać niczego.
Servy
1
Również bezpieczeństwo jest powodem do korzystania z niego! Rozważ aplikację w trybie piaskownicy próbującą uzyskać dostęp do pliku. Piaskownica może przetestować ciągi nazw plików, ale co, jeśli osoba atakująca może wysłać ci zmienną, 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 Java Stringjest oznaczona final(podobnie do sealed) i założę się, że ta sama logika stanowi podstawę niektórych decyzji w języku C #.
Darien
4

Gdy klasa jest zapieczętowana, kod używający tego typu może dokładnie wiedzieć, z czym ma do czynienia.

Teraz w C # stringjest sealed, 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ę:

public class EvilString// : String
{
    public override string ToString()
    {
        throw new Exception("I'm mean");
    }
    public override bool Equals(object obj)
    {
        return false;
    }
    public override int GetHashCode()
    {
        return 0;
    }
}

To byłaby teraz klasa strun, która narusza wszelkiego rodzaju właściwości, o których projektanci stringwiedzieli, że są prawdziwe. Wiedzieli, że Equalsmetoda 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, że ToStringmetoda stringnie 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 dlasealedprojektanci 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.

Servy
źródło
Ale możesz złamać to założenie w swoim kodzie . To nie tak, że chwytasz źródło Stringa i edytujesz je. Projektanci nie musieliby tego rozliczać, ponieważ korzysta z niego 1 liść, 1 gałąź, 1 klient na własne ryzyko i według własnego uznania. Stamtąd nie będzie się rozpowszechniał, chyba że inni klienci wolą to zrobić. I tak dalej.
cregox
3
@Cawas A jeśli wyjątek zostanie zgłoszony w nieoczekiwanym czasie i w wyniku tego nastąpi wyciek zasobów lub klasa zostanie pozostawiona w nieokreślonym stanie i będzie funkcjonować niewłaściwie? Ten przypadek jest trochę głupi, ale łatwo może się zdarzyć, że ktoś napisze implementację, która według niego jest sensowna, ale czasami rzuca się, gdy nie powinna lub nie działa z pewnymi wartościami. Będą wtedy narzekać, gdy kod języka nie będzie działał. Lepiej jest po prostu nie pozwalać ludziom robić rzeczy, których język nie będzie w stanie obsłużyć.
Servy
W każdym razie mówisz o budowaniu kompilatora. Istnieje wiele takich sealedwbudowanych 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śnia sealedistnienia.
cregox
2
@Cawas stringTyp 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.
Servy
4
@Cawas: Myślę, że możesz tutaj pominąć aspekt obsługi klienta. Jeśli pozwolisz, aby klasa była dziedziczona, klienci odziedziczą po niej, a następnie zadzwonią, gdy się zepsuje. Możesz im powiedzieć przez cały dzień, że dziedziczą po tej klasie na własne ryzyko, ale nadal otrzymujesz połączenie, ponieważ pozwoliłeś im dziedziczyć po nim, więc zakładają, że jest to bezpieczne.
Robert Harvey
3

Czy jest jakiś dobry powód, aby używać Sealed?

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.

Załóżmy, że istnieje dobry powód, aby używać zapieczętowanego i powinniśmy używać go jako domyślnego dla wszystkiego, co robimy z dziedziczeniem?

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 sealedmoże to być dobre, oraz przypadki, w których powoduje to smutek - tak jak każdy inny kompromis.

Telastyn
źródło
Dla mnie to i „aby uniknąć konieczności wspierania dziedziczenia dla klientów”, których Robert nie chce opracowywać, są najbardziej obiecującymi odpowiedziami ... Może mógłbyś bardziej szczegółowo omówić te przypadki, w których korzystasz sealed, proszę?
cregox
@Cawas - Nie jestem pewien, czy dam radę. Bardzo rzadko to robię i często jest to kompromis, w którym gwarancja, że ​​coś pozostanie niezmienne (w celu optymalizacji wydajności, uproszczenia projektu) jest warta niezdolności do dziedziczenia po obiekcie.
Telastyn
To super. Dunk już go zanurzył! : P Dziękuję bardzo za odpowiedź. I jako jedyny dostrzegłeś moje „subtelne wskazówki” na temat mojej wiedzy. Wygląda na to, że ludzie zakładali, że wiedziałem więcej, nie wiem ... Ale chciałbym, żeby „tylko kodowanie” wyciągnęło mnie z tej skały. Może stosuję te koncepcje w jakiś sposób, ale nie tak proceduralnie, jak powinienem.
cregox
3

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. sealedjest 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.

Stephen
źródło
Pierwsza część została już wskazana w komentarzach do pytania. Druga część to po prostu więcej rozumowania bez prawdziwych związków przyczynowych. Trzecia część, choć już podchodzona gdzie indziej, może mieć coś do tego. Skoncentrowałbym się na tym.
cregox
trzeci punkt nie zachęca do kompozycji zamiast dziedziczenia, całkowicie eliminuje dziedziczenie jako opcję, nawet jeśli byłby to najlepszy ogólny wybór.
Michael Shaw
To zależy, czy kontrolujesz bazę kodu, czy nie. Jeśli zapieczętujesz klasy, zawsze możesz je odblokować później, jeśli zajdzie taka potrzeba.
Stephen
2

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.

Euforyk
źródło
To jest bardziej jak to! To może być dobry powód ... „Ponieważ ludzie chcą to zamknąć”. I to też próbuję powiedzieć w pytaniu: wygląda na to, że klasy nie-wirtualne w C ++ mogą zostać zastąpione. 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ć”!
cregox
4
Nie dlatego metody klasy C ++ domyślnie nie są wirtualne. Uczynienie metody wirtualną oznacza, że ​​musi być dodatkowy narzut w tabeli odnośników metody, a C ++ ma filozofię, że płacisz za narzut tylko wtedy, gdy chcesz tej funkcji.
Michael Shaw
@Ptolemy Ok, usunąłem go. Nigdy nie powinienem był tego pisać, ponieważ wiedziałem, że ludzie zaczynają na to narzekać.
Euforii
hej, ja zupełnie oczywiste, że jeśli chcesz, by twierdzić, że ludzie chcą pisać zamkniętych klas domyślnie i muszą aktywnie wybrać, aby otworzyć je potem będziesz musiał chwycić się brzytwy
Michael Shaw
IMO jest bardzo podobny do prywatnych / chronionych / publicznych debat, jeśli chodzi o interfejsy API: To zależy w dużej mierze od tego, kto kontroluje klasę, kto chce odziedziczyć klasę, komunikację między nimi i jak często pojawiają się nowe wersje.
Darien
-2

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.

  1. 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.

  2. 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

  3. 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.

Michael Shaw
źródło
2
/ me wysyła wiadomość e-mail do firmy Microsoft. Czy możesz otworzyć dla mnie klasę X i wysłać nową wersję .NET. Pozdrawiam, .... Jak powiedziałem, jedyny raz w rozwoju komercyjnym widziałem zamkniętą klasę, nie było możliwe jej odblokowanie.
Michael Shaw
6
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ę.
Robert Harvey
5
Gdybyś mógł go rozszczelnić, nie byłoby sensu go uszczelniać, prawda?
Robert Harvey
2
@Ptolemy Zezwolenie ludziom na rozszerzenie klasy jest cechą . Taki, który pochłania znaczną część wysiłku rozwojowego. Jest to funkcja, którą warto uzasadnić tylko wtedy, gdy autor klasy widzi dostateczne korzyści w potencjalnych rozszerzeniach, aby uzasadnić wysiłek. Jeśli nie mogą uzasadnić wysiłku, a zatem typ i jego użytkownicy nie są zbudowani do obsługi rozszerzenia, ważne jest, aby temu zapobiec, w przeciwnym razie technicznie byłoby możliwe rozszerzenie typu, ale jest mało prawdopodobne, aby działał poprawnie, gdy ty to robisz.
Servy
2
@Cawas: Po prostu jesteś trudny.
Robert Harvey
-7

Używam sealedsłowa kluczowego tylko w klasach, które zawierają tylko metody statyczne.

Vladimir Kocjancic
źródło
9
Dlaczego więc po prostu nie oznaczyć klasy jako static?
Servy
@Servy ... Dobra uwaga.
Vladimir Kocjancic
-8

Jak wynika z mojego pytania, nie jestem tutaj ekspertem. Ale nie widzę powodu, sealedby 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ć.

Cregox
źródło
2
„Problem rozwiązany” - Dokładnie.
Robert Harvey
@RobertHarvey Nie zostało rozwiązane, jak wyjaśniono w następnym akapicie.
cregox
8
Jesteś po prostu zły, ponieważ nie możesz przedłużyć zajęć. To nie znaczy, że sealednie 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.
Robert Harvey
Nigdy nie powiedziałem, że to nie jest przydatne. Mówię, że nie widzę jego użycia i wyjaśniam, dlaczego. Błagam, daj mi dobry powód, aby z niego skorzystać! Nawet jeśli jest to po prostu „ponieważ chcę przestrzegać zamkniętego wzoru”, to w porządku. Nie widzę, żeby ktoś podał ten powód, wprost.
cregox
7
Dobrym powodem jest to, że nie musisz wspierać dziedziczenia w zapieczętowanej klasie.
Robert Harvey