Po co zapieczętować klasę?

96

Chciałbym usłyszeć, jaka jest motywacja stojąca za większością zamkniętych klas w środowisku .Net. Jaka jest korzyść z zapieczętowania klasy? Nie mogę pojąć, jak niedopuszczenie do dziedziczenia może być przydatne i najprawdopodobniej nie jest jedyną osobą walczącą z tymi klasami.

Dlaczego więc framework jest zaprojektowany w ten sposób i czy nie byłaby to nieodwracalna zmiana polegająca na rozpakowaniu wszystkiego? Musi być inny powód, ale po prostu bycie złym?

mmiika
źródło

Odpowiedzi:

41
  • Czasami klasy są zbyt cenne i nie są przeznaczone do dziedziczenia.
  • Środowisko uruchomieniowe / odbicie może przyjmować założenia dotyczące dziedziczenia dotyczące klas zapieczętowanych podczas wyszukiwania typów. Świetnym tego przykładem jest - zaleca się zapieczętowanie atrybutów w celu zwiększenia szybkości działania wyszukiwania. type.GetCustomAttributes (typeof (MyAttribute)) będzie działać znacznie szybciej, jeśli MyAttribute jest zapieczętowany.

Artykuł MSDN dotyczący tego tematu to Ograniczanie rozszerzalności przez klasy uszczelniające .

CVertex
źródło
3
Cieszę się, że teraz wyraźnie mówią „używaj z rozwagą”… jednak chcieliby, aby praktykowali to, co głoszą.
mmiika
13
To dla mnie zła rada :(
Jon Skeet,
4
@CVertex: Przepraszam, nie próbowałem cię krytykować - tylko artykuł.
Jon Skeet
16
@generalt: Wierzę w projektowanie do dziedziczenia lub w zakazanie tego. Projektowanie pod kątem dziedziczenia wymaga sporo pracy i często ogranicza implementacje w przyszłości. Dziedziczenie wprowadza również niepewność do dzwoniących co do tego, do czego będą dzwonić. Nie łączy się też dobrze z niezmiennością (której jestem fanem). Dziedziczenie klas jest przydatne tylko w stosunkowo niewielkiej liczbie miejsc (podczas gdy uwielbiam interfejsy).
Jon Skeet,
1
@CVertex Jeśli korzystałeś z platformy .NET, prawdopodobnie napotkałeś problem i po prostu go nie zauważyłeś, prawie wszystkie klasy .NET core są zapieczętowane.
CoryG
105

Klasy powinny być przeznaczone do dziedziczenia lub zakazywać tego. Projektowanie do dziedziczenia wiąże się z kosztami:

  • Może sprecyzować twoją implementację (musisz zadeklarować, które metody będą wywoływać inne metody, na wypadek, gdyby użytkownik przesłaniał jedną, ale nie drugą)
  • Ujawnia twoją implementację, a nie tylko efekty
  • Oznacza to, że podczas projektowania musisz myśleć o większej liczbie możliwości
  • Rzeczy takie jak Equals są trudne do zaprojektowania w drzewie dziedziczenia
  • Wymaga więcej dokumentacji
  • Niezmienny typ, który jest podklasą, może stać się zmienny (ick)

Punkt 17 Efektywnej Java zawiera więcej szczegółów na ten temat - niezależnie od tego, że została napisana w kontekście Javy, porada dotyczy również .NET.

Osobiście chciałbym, żeby klasy były domyślnie zapieczętowane w .NET.

Jon Skeet
źródło
26
Hmm ... jeśli przedłużysz klasę, czy to nie twój problem, jeśli ją złamiesz?
mmiika
25
Co jeśli zmiana implementacji, nad którą nie masz kontroli w klasie bazowej, złamie cię? Czyja to wina? Zasadniczo dziedziczenie wprowadza kruchość. Preferowanie składu nad dziedziczeniem sprzyja solidności, IMO.
Jon Skeet
6
Tak, interfejsy są fajne - i tak, i tak możesz preferować kompozycję. Ale jeśli ujawnię niezapieczętowaną klasę bazową bez dokładnego przemyślenia, powinienem oczekiwać, że zmiany mogą równie dobrze uszkodzić klasy pochodne. To dla mnie złe uczucie. Lepiej zapieczętować klasę i uniknąć pęknięcia, IMO.
Jon Skeet
8
@Joan: Kompozycja to relacja „ma”, a nie „jest”. Więc jeśli chcesz napisać klasę, która może zachowywać się jak lista pod pewnymi względami, ale nie w innych, możesz chcieć utworzyć klasę ze zmienną składową List <T>, zamiast wywodzić się z List <T>. Następnie użyjesz listy do zaimplementowania różnych metod.
Jon Skeet
3
@ThunderGr: Ale o to mi chodzi: kiedy nie musisz się martwić o to, jakie inne implementacje są dostarczane przez podklasy, możesz być bardziej swobodny dzięki własnej implementacji. Załóżmy na przykład, że jedna metoda jest implementowana przez wywołanie innej metody i obie są wirtualne. Należy to udokumentować - nawet jeśli wydaje się, że jest to szczegół implementacji. Zasadniczo projektowanie do dziedziczenia wymaga kajdanek - które są odpowiednie w niektórych przypadkach, ale nie w innych. Wolałbym mieć zapieczętowaną klasę implementującą interfejs niż niezapieczętowaną klasę z wieloma metodami wirtualnymi.
Jon Skeet
8

Wygląda na to, że oficjalne wytyczne Microsoftu dotyczące uszczelniania ewoluowały od czasu zadania tego pytania ~ 9 lat temu i przeszły od filozofii opt-in (domyślnie pieczęć) do rezygnacji (domyślnie nie pieczętuj):

X NIE pieczętować zajęć bez dobrego powodu, aby to zrobić.

Uszczelnianie klasy, ponieważ nie możesz wymyślić scenariusza rozszerzalności, nie jest dobrym powodem. Użytkownicy frameworka lubią dziedziczyć po klasach z różnych nieoczywistych powodów, takich jak dodawanie wygodnych członków. Zobacz Unsealed Classes, aby zapoznać się z przykładami nieoczywistych powodów, dla których użytkownicy chcą dziedziczyć z typu.

Dobre powody do zaplombowania klasy to:

  • Klasa jest klasą statyczną. Zobacz Projekt klasy statycznej.
  • Klasa przechowuje poufne informacje poufne w dziedziczonych chronionych elementach członkowskich.
  • Klasa dziedziczy wielu wirtualnych członków, a koszt ich indywidualnego zapieczętowania przeważałby nad korzyściami wynikającymi z pozostawienia klasy niezamkniętej.
  • Klasa jest atrybutem wymagającym bardzo szybkiego wyszukiwania w czasie wykonywania. Atrybuty zapieczętowane mają nieco wyższy poziom wydajności niż atrybuty niezapieczętowane. Zobacz atrybuty.

X NIE deklaruj chronionych lub wirtualnych członków na zapieczętowanych typach.

Z definicji nie można dziedziczyć zapieczętowanych typów. Oznacza to, że nie można wywołać chronionych elementów członkowskich na typach zapieczętowanych, a metody wirtualne na typach zapieczętowanych nie mogą zostać zastąpione.

✓ ROZWAŻ elementy uszczelniające, które zastępujesz. Problemy, które mogą wyniknąć z wprowadzenia wirtualnych członków (omówione w Wirtualnych członkach), dotyczą również zastąpień, choć w nieco mniejszym stopniu. Zapieczętowanie zastąpienia chroni Cię przed tymi problemami, począwszy od tego punktu w hierarchii dziedziczenia.

Rzeczywiście, jeśli przeszukasz bazę kodu ASP.Net Core , znajdziesz tylko około 30 wystąpień sealed class, z których większość to atrybuty i klasy testowe.

Uważam, że ochrona niezmienności jest dobrym argumentem za uszczelnieniem.

Ohad Schneider
źródło
4

Znalazłem to zdanie w dokumentacji msdn: „Klasy zapieczętowane są używane głównie do zapobiegania wyprowadzaniu. Ponieważ nigdy nie mogą być używane jako klasy bazowe, niektóre optymalizacje w czasie wykonywania mogą nieco przyspieszyć wywoływanie zapieczętowanych elementów klas.”

Nie wiem, czy wydajność jest jedyną zaletą zajęć zamkniętych i osobiście chciałbym poznać inne powody ...

bruno conde
źródło
4
Byłoby interesujące zobaczyć, o jakich korzyściach wydajnościowych mówią ...
mmiika
3

Wydajność jest ważnym czynnikiem, na przykład klasa string w java jest ostateczna (<- zapieczętowana), a powodem tego jest tylko wydajność. Myślę, że kolejnym ważnym punktem jest uniknięcie kruchego problemu z klasą bazową opisanego szczegółowo tutaj: http://blogs.msdn.com/ericlippert/archive/2004/01/07/virtual-methods-and-brittle-base-classes. aspx

Jeśli zapewnisz strukturę, jest to ważne dla starszych projektów z możliwością konserwacji i uaktualnienia struktury, aby uniknąć kruchego problemu z klasą bazową

Peter Parker
źródło
Powodem, dla którego String w java jest ostateczny, nie jest wydajność, ale bezpieczeństwo.
CesarB
@CesarB: Tak, ale również String nie jest zwykłą klasą Javy. Jest to jedyna (jak sądzę) klasa w Javie, która obsługuje przeciążanie operatorów (więcej w tej sekcji: „Nawet C i Java mają (zakodowane) przeciążenie operatorów”), co nie jest możliwe w normalnej klasie. Z tego powodu Stringklasa może nawet nie być możliwa do podklasy, nawet jeśli nie była ostateczna.
wchargin
1

Sealed służy do zapobiegania „problemowi z kruchą klasą podstawową”. Znalazłem dobry artykuł w MSDN, który to wyjaśnia.

ihebiheb
źródło
0

Uszczelnienie pozwala uzyskać niewielki wzrost wydajności. Jest to mniej prawdziwe w świecie JIT i leniwej pesymizacji niż w świecie, powiedzmy C ++, ale ponieważ .NET nie jest tak dobry jak pesymizacja, jak kompilatory java, głównie z powodu różnych filozofii projektowania, nadal jest przydatny. Mówi kompilatorowi, że może bezpośrednio wywoływać dowolne metody wirtualne, zamiast wywoływać je pośrednio przez vtable.

Jest to również ważne, gdy chcesz mieć „zamknięty świat” na przykład przy porównywaniu równości. Zwykle kiedy już zdefiniuję metodę wirtualną, jestem prawie gotowy do zdefiniowania pojęcia porównania równości, które naprawdę realizuje tę ideę. Z drugiej strony mógłbym być w stanie zdefiniować to dla konkretnej podklasy klasy za pomocą metody wirtualnej. Zapieczętowanie tej klasy gwarantuje, że równość naprawdę obowiązuje.

Edward KMETT
źródło
0

Uszczelnienie klasy ułatwia zarządzanie zasobami jednorazowymi.

Jeff Dunlop
źródło
0

Aby określić, czy należy zapieczętować klasę, metodę lub właściwość, należy ogólnie wziąć pod uwagę następujące dwa punkty:

• Potencjalne korzyści, jakie może przynieść wyprowadzenie klas dzięki możliwości dostosowywania klasy.

• Potencjał, że klasy pochodne mogą zmodyfikować twoje klasy w taki sposób, że nie będą już działać poprawnie lub zgodnie z oczekiwaniami.

user3629577
źródło
0

Kolejną kwestią jest to, że zapieczętowane klasy nie mogą zostać pominięte w testach jednostkowych. Z dokumentacji Microsoft :

Klasy zapieczętowane lub metody statyczne nie mogą zostać zamknięte, ponieważ typy pośredniczące polegają na wysyłaniu metod wirtualnych. W takich przypadkach użyj typów podkładek, jak opisano w temacie Używanie podkładek, aby odizolować aplikację od innych zestawów do testowania jednostkowego

Dave Clark
źródło