Dlaczego metody statyczne nie powinny być nadrzędne?

13

W odpowiedziach na to pytanie, ogólny konsensus był taki, że metody statyczne nie mają być nadpisywane (a zatem funkcje statyczne w języku C # nie mogą być wirtualne ani abstrakcyjne). Jest to jednak nie tylko przypadek w języku C #; Java również tego zabrania, a C ++ też to nie lubi. Mogę jednak wymyślić wiele przykładów funkcji statycznych, które chciałbym zastąpić w klasie potomnej (na przykład metody fabryczne). Chociaż teoretycznie istnieją sposoby na ich obejście, żaden z nich nie jest czysty ani prosty.

Dlaczego funkcje statyczne nie powinny być nadpisywane?

PixelArtDragon
źródło
4
Delphi obsługuje metody klasowe , które są dość podobne do metod statycznych i mogą zostać zastąpione. Dostają selfwskaźnik, który wskazuje na klasę, a nie na instancję klasy.
CodesInChaos
Jedna z definicji statycznych to: brak zmian. Jestem ciekawy, dlaczego stworzyłeś funkcję statyczną, którą chcesz zastąpić.
JeffO
4
@JeffO: Ta angielska definicja „static” nie ma absolutnie nic wspólnego z unikalną semantyką statycznych funkcji składowych.
Wyścigi lekkości na orbicie
5
Twoje pytanie brzmi: „dlaczego statyczne metody powinny korzystać z wysyłki o typie statycznym zamiast z pojedynczą wirtualną wysyłką?” Kiedy sformułujesz pytanie w ten sposób, myślę, że samo ono odpowiada.
Eric Lippert,
1
@EricLippert Pytanie to można lepiej sformułować jako „Dlaczego C # oferuje metody statyczne zamiast metod klasowych?”, Ale nadal jest to prawidłowe pytanie.
CodesInChaos

Odpowiedzi:

13

W przypadku metod statycznych nie ma obiektu zapewniającego odpowiednią kontrolę mechanizmu nadpisywania.

Mechanizm wirtualnej metody normalnej klasy / instancji pozwala na precyzyjnie dostrojone sterowanie nadpisywaniem w następujący sposób: każdy rzeczywisty obiekt jest instancją dokładnie jednej klasy. Ta klasa określa zachowanie przesłonięć; zawsze dostaje pierwszy crack przy metodach wirtualnych. Następnie może wybrać wywołanie metody nadrzędnej we właściwym czasie w celu jej implementacji. Następnie każda metoda nadrzędna otrzymuje swoją kolej, aby wywołać metodę nadrzędną. Powoduje to ładną kaskadę wywołań nadrzędnych, która realizuje jedno z pojęć ponownego użycia kodu, z którego znana jest orientacja obiektowa. (W tym przypadku kod bazowy / superklasy są ponownie wykorzystywane w stosunkowo skomplikowany sposób; innym ortogonalnym pojęciem ponownego użycia kodu w OOP jest po prostu posiadanie wielu obiektów tej samej klasy.)

Klasy podstawowe mogą zostać ponownie wykorzystane przez różne podklasy i każda z nich może wygodnie współistnieć. Każda klasa używana do tworzenia obiektów dyktujących własne zachowanie, pokojowo i jednocześnie współistniejących z innymi. Klient ma kontrolę nad tym, jakich zachowań chce i kiedy, wybierając klasę, której ma użyć, aby utworzyć obiekt i przekazać go innym, zgodnie z potrzebami.

(To nie jest doskonały mechanizm, ponieważ zawsze można zidentyfikować funkcje, które nie są obsługiwane, oczywiście, dlatego właśnie wzorce, takie jak metoda fabryczna i wstrzykiwanie zależności, są nakładane na wierzch).

Tak więc, jeśli mielibyśmy zrobić funkcję zastępowania dla statyki bez zmiany czegokolwiek innego, mielibyśmy trudności z zamówieniem przesłonięć. Trudno byłoby zdefiniować ograniczony kontekst dla zastosowania przesłonięcia, więc można byłoby przesłonić globalnie, a nie lokalnie, jak w przypadku obiektów. Nie ma obiektu instancji, który mógłby zmienić zachowanie. Więc jeśli ktoś wywołał metodę statyczną, która została zastąpiona przez inną klasę, to czy zastąpienie powinno uzyskać kontrolę, czy nie? Jeśli istnieje wiele takich zastąpień, kto pierwszy przejmie kontrolę? druga? W przypadku zastąpienia obiektu instancji wszystkie te pytania mają sensowne i dobrze uzasadnione odpowiedzi, ale nie mają ich w przypadku statyki.

Zastąpienia statyki byłyby zasadniczo chaotyczne i takie rzeczy były już wcześniej robione.

Na przykład Mac OS System 7 i wcześniej korzystały z mechanizmu łatania pułapek w celu rozszerzenia systemu poprzez uzyskanie kontroli nad wywołaniami systemowymi wykonywanymi przez aplikację przed systemem operacyjnym. Można by pomyśleć o systemowej tablicy łatek wywołań systemowych jako tablicy wskaźników funkcji, podobnie jak na przykład vtable dla obiektów, z tym wyjątkiem, że była to pojedyncza tabela globalna.

Spowodowało to nieopisany smutek dla programistów z powodu nieuporządkowanej natury łatania pułapek. Ktokolwiek mógł załatać pułapkę, po prostu wygrał, nawet jeśli nie chciał. Każda łata pułapki przechwytywałaby poprzednią wartość pułapki dla pewnego rodzaju możliwości wywołania rodzica, co było wyjątkowo delikatne. Usunięcie łaty pułapkowej, powiedzmy, że kiedy nie musisz już wiedzieć o wywołaniu systemowym, uznano ją za złą formę, ponieważ tak naprawdę nie posiadałeś informacji potrzebnych do usunięcia łatki (gdybyś to zrobił, cofnąłbyś również wszelkie inne łaty, które pojawiły się później ty).

Nie oznacza to, że niemożliwe byłoby stworzenie mechanizmu zastępowania statyki, ale prawdopodobnie wolałbym zamiast tego zmienić pola statyczne i metody statyczne w pola instancji i metody instancji metaklas, tak aby normalny obiekt Zastosowanie miałyby wówczas techniki orientacji. Zauważ, że istnieją również systemy, które to robią: CSE 341: klasy i metaklasy Smalltalk ; Zobacz także: Co to jest odpowiednik Smalltalk w statyce Java?


Próbuję powiedzieć, że musiałbyś zrobić poważny projekt funkcji językowych, aby działał nawet w miarę dobrze. Na przykład zastosowano podejście naiwne, utykało, ale było bardzo problematyczne i prawdopodobnie (tj. Argumentowałbym) architektonicznie wadliwe, zapewniając niepełną i trudną do użycia abstrakcję.

Zanim skończysz projektować funkcję przesłonięć statycznych tak, aby działała dobrze, być może wymyśliłeś jakąś formę metaklasy, która jest naturalnym rozszerzeniem OOP na metody klasowe. Nie ma więc powodu, aby tego nie robić - a niektóre języki faktycznie tak robią. Być może jest to tylko trochę dodatkowy wymóg, że wiele języków decyduje się tego nie robić.

Erik Eidt
źródło
Jeśli dobrze rozumiem: nie chodzi o to, czy teoretycznie chcesz lub powinieneś zrobić coś takiego, ale bardziej programowo, wdrożenie tego byłoby trudne i niekoniecznie przydatne?
PixelArtDragon
@Garan, patrz moje uzupełnienie.
Erik Eidt,
1
Nie kupuję argumentu zamówienia; otrzymujesz metodę dostarczoną przez klasę, na której ją wywołałeś (lub pierwszą nadklasę, która udostępnia metodę zgodnie z wybraną przez Ciebie językiem linearyzacją).
hobbs
1
@PierreArlaud: Ale this.instanceMethod()ma dynamiczną rozdzielczość, więc jeśli self.staticMethod()ma taką samą rozdzielczość, a mianowicie dynamiczną, nie byłaby to już metoda statyczna.
Jörg W Mittag,
1
należy dodać nadrzędną semantykę dla metod statycznych. Hipotetyczne metaklasy Java + nie muszą niczego dodawać! Po prostu tworzysz obiekty klas, a metody statyczne stają się metodami instancji zwykłych. Nie musisz dodawać czegoś do języka, ponieważ używasz tylko istniejących już pojęć: obiektów, klas i metod instancji. Jako bonus faktycznie usuwasz metody statyczne z języka! Możesz dodać funkcję, usuwając pojęcie, a tym samym złożoność z języka!
Jörg W Mittag,
9

Przesłanianie zależy od wirtualnej wysyłki: używasz typu środowiska wykonawczego thisparametru, aby zdecydować, którą metodę wywołać. Metoda statyczna nie ma thisparametru, więc nie ma nic do wysłania.

Niektóre języki, zwłaszcza Delphi i Python, mają zakres „pośredni”, który pozwala na to: metody klasowe. Metoda klasowa nie jest zwykłą metodą instancji, ale nie jest też statyczna; otrzymuje selfparametr (zabawnie, oba języki nazywają go thisparametrem self), który jest odniesieniem do samego typu obiektu, a nie do instancji tego typu. Dzięki tej wartości masz teraz typ do wirtualnej wysyłki.

Niestety ani JVM, ani CLR nie ma nic porównywalnego.

Mason Wheeler
źródło
Czy używają języków, w selfktórych klasy są rzeczywistymi obiektami tworzonymi za pomocą instancji z nadpisywalnymi metodami - oczywiście Smalltalk, Ruby, Dart, Objective C, Self, Python? W porównaniu z językami używanymi po C ++, gdzie klasy nie są obiektami pierwszej klasy, nawet jeśli istnieje pewien dostęp do refleksji?
Jerry101
Mówiąc wprost, czy mówisz, że w Pythonie lub Delphi możesz przesłonić metody klasowe?
Erik Eidt,
@ErikEidt W Pythonie prawie wszystko można zastąpić. W Delphi metodę klasy można jawnie zadeklarować, virtuala następnie zastąpić w klasie potomnej, podobnie jak metody instancji.
Mason Wheeler,
Te języki dostarczają pewnego rodzaju pojęcie metaklasy, nie?
Erik Eidt,
1
@ErikEidt Python ma „prawdziwe” metaklasy. W Delphi odwołanie do klasy jest specjalnym typem danych, a nie samą klasą.
Mason Wheeler,
3

Ty pytasz

Dlaczego funkcje statyczne nie powinny być nadpisywane?

pytam

Dlaczego funkcje, które chcesz zastąpić, powinny być statyczne?

Niektóre języki zmuszają cię do rozpoczęcia pokazu w sposób statyczny. Ale potem naprawdę możesz rozwiązać wiele problemów bez żadnych statycznych metod.

Niektóre osoby lubią stosować metody statyczne, gdy nie ma zależności od stanu w obiekcie. Niektórzy ludzie lubią stosować metody statyczne do budowy innych obiektów. Niektórzy ludzie lubią unikać metod statycznych w jak największym stopniu.

Żadna z tych osób nie ma racji.

Jeśli potrzebujesz zastąpić go, po prostu przestań oznaczać go statycznie. Nic się nie zepsuje, bo latają wokół ciebie bezpaństwowce.

candied_orange
źródło
Jeśli język obsługuje struktury, wówczas typ jednostki może być strukturą o zerowej wielkości, która jest przekazywana do metody wprowadzania logicznego. Stworzenie tego obiektu jest szczegółem implementacji. A jeśli zaczniemy mówić o szczegółach implementacji jako rzeczywistą prawdą, to myślę, że trzeba także wziąć pod uwagę, że w realizacji rzeczywistych, typu zero-sized nie musi być faktycznie instancja (ponieważ żadne instrukcje są potrzebne do obciążenia to lub przechowuj).
Theodoros Chatzigiannakis
A jeśli mówisz jeszcze bardziej „za kulisami” (np. Na poziomie pliku wykonywalnego), wówczas argumenty środowiskowe można zdefiniować jako typ w języku, a „prawdziwy” punkt wejścia programu może być metodą instancji tego typu, działając na dowolną instancję przekazaną do programu przez moduł ładujący system operacyjny.
Theodoros Chatzigiannakis
Myślę, że to zależy od tego, jak na to patrzysz. Na przykład, gdy program jest uruchamiany, system operacyjny ładuje go do pamięci, a następnie przechodzi pod adres, na którym rezyduje programowa implementacja binarnego punktu wejścia (np. _start()Symbol w systemie Linux). W tym przykładzie plik wykonywalny jest obiektem (ma własną „tabelę wysyłki” i wszystko), a punktem wejścia jest operacja wywoływana dynamicznie. W związku z tym punkt wejścia programu jest z natury wirtualny, gdy jest oglądany z zewnątrz, i można łatwo sprawić, że będzie wyglądał wirtualny, również patrząc od wewnątrz.
Theodoros Chatzigiannakis
1
„Nic się nie zepsuje, bo latają wokół ciebie bezpaństwowce”. ++++++
RubberDuck
@TheodorosChatzigiannakis masz silny argument. jeśli nic innego mnie nie przekonałeś, linia nie inspiruje linii myśli, którą zamierzałem. Naprawdę starałem się dać ludziom pozwolenie na zlekceważenie niektórych bardziej zwyczajowych praktyk, które ślepo kojarzą z metodami statycznymi. Więc zaktualizowałem. Myśli?
candied_orange
2

Dlaczego metody statyczne nie powinny być nadrzędne?

To nie jest kwestia „powinna”.

„Przesłanianie” oznacza „wysyłanie dynamiczne ”. „Metoda statyczna” oznacza „wysyłkę statyczną”. Jeśli coś jest statyczne, nie można go zastąpić. Jeśli coś można zastąpić, nie jest statyczne.

Twoje pytanie jest raczej podobne do pytania: „Dlaczego trójkołowce nie powinny mieć czterech kół?” Definicja z „motocykl” jest to, że ma trzy koła. Jeśli jest to trójkołowy, nie może mieć czterech kół, jeśli ma cztery koła, nie może być trójkołowy. Podobnie definicja „metody statycznej” polega na tym, że jest ona wysyłana statycznie. Jeśli jest to metoda statyczna, nie może być dynamicznie wysłana, jeśli może być dynamicznie wysłana, nie może być metodą statyczną.

Oczywiście możliwe jest zastosowanie metod klas, które można zastąpić. Lub możesz mieć język taki jak Ruby, w którym klasy są obiektami tak jak każdy inny obiekt, a zatem mogą mieć metody instancji, co całkowicie eliminuje potrzebę metod klasowych. (Ruby ma tylko jeden rodzaj metod: metody instancji. Nie ma metod klas, metod statycznych, konstruktorów, funkcji ani procedur.)

Jörg W Mittag
źródło
1
„Z definicji” Dobrze powiedziane.
candied_orange
1

dlatego funkcje statyczne w języku C # nie mogą być wirtualne ani abstrakcyjne

W języku C # zawsze wywołujesz członków statycznych za pomocą klasy, np . BaseClass.StaticMethod()Nie baseObject.StaticMethod(). Więc w końcu, jeśli ChildClassdziedziczysz BaseClassi childObjectwystąpienie ChildClass, nie będziesz w stanie wywołać metody statycznej childObject. Zawsze będziesz musiał jawnie użyć prawdziwej klasy, więc po static virtualprostu nie ma sensu.

Możesz na nowo zdefiniować tę samą staticmetodę w klasie podrzędnej i użyć newsłowa kluczowego.

class BaseClass {
    public static int StaticMethod() { return 1; }
}

class ChildClass {
    public static new int StaticMethod() { return BaseClass.StaticMethod() + 2; }
}

int result;    
var baseObj = new BaseClass();
var childObj = new ChildClass();

result = BaseClass.StaticMethod();
result = baseObj.StaticMethod(); // DOES NOT COMPILE

result = ChildClass.StaticMethod();
result = childObj.StaticMethod(); // DOES NOT COMPILE

Gdyby można było zadzwonić baseObject.StaticMethod(), twoje pytanie miałoby sens.

użytkownik276648
źródło