Perché i metodi statici non dovrebbero essere sostituibili?


13

Nelle risposte a questa domanda, il consenso generale era che i metodi statici non sono pensati per essere sovrascritti (e quindi le funzioni statiche in C # non possono essere virtuali o astratte). Questo non è solo il caso in C #, comunque; Anche Java lo proibisce e neanche C ++ sembra gradirlo. Tuttavia, posso pensare a molti esempi di funzioni statiche che vorrei sovrascrivere in una classe figlio (ad esempio, metodi di fabbrica). Mentre in teoria ci sono modi per aggirarli, nessuno di essi è pulito o semplice.

Perché le funzioni statiche non dovrebbero essere sostituibili?


4
Delphi supporta i metodi di classe , che sono abbastanza simili ai metodi statici e possono essere sovrascritti. Viene passato un selfpuntatore che punta alla classe e non a un'istanza della classe.
CodesInCos

Una definizione di statico è: mancanza di cambiamento. Sono curioso di sapere perché hai reso statica una funzione che desideri ignorare.
JeffO,

4
@JeffO: Quella definizione inglese di "statico" non ha assolutamente nulla a che fare con la semantica univoca delle funzioni statiche dei membri.
Lightness Races with Monica

5
La tua domanda è "perché i metodi statici dovrebbero usare la spedizione tipizzata staticamente invece della spedizione singola virtuale ?" Quando pronunci la domanda in questo modo penso che risponda a se stessa.
Eric Lippert,

1
@EricLippert La domanda potrebbe essere formulata come "Perché C # offre metodi statici anziché metodi di classe?", Ma è ancora una domanda valida.
CodesInChaos,

Risposte:


13

Con i metodi statici, non esiste alcun oggetto che fornisca il controllo adeguato del meccanismo di override.

Il normale meccanismo del metodo virtuale di classe / istanza consente un controllo finemente regolato delle sostituzioni come segue: ogni oggetto reale è un'istanza esattamente di una classe. Quella classe determina il comportamento delle sostituzioni; ottiene sempre il primo crack nei metodi virtuali. Può quindi scegliere di chiamare il metodo parent al momento giusto per la sua implementazione. Ogni metodo genitore quindi ottiene anche il suo turno per invocare il suo metodo genitore. Ciò si traduce in una bella cascata di invocazioni padre, che compie una delle nozioni di riutilizzo del codice per cui è noto l'orientamento agli oggetti. (Qui il codice delle classi base / super viene riutilizzato in un modo relativamente complesso; un'altra nozione ortogonale di riutilizzo del codice in OOP è semplicemente avere più oggetti della stessa classe.)

Le classi di base possono essere riutilizzate da varie sottoclassi e ognuna può coesistere comodamente. Ogni classe utilizzata per creare un'istanza di oggetti dettando il proprio comportamento, coesistendo pacificamente e contemporaneamente con gli altri. Il cliente ha il controllo su quali comportamenti desidera e quando scegliendo quale classe utilizzare per creare un'istanza di un oggetto e passare ad altri come desiderato.

(Questo non è un meccanismo perfetto, in quanto si possono sempre identificare capacità che non sono supportate, ovviamente, motivo per cui modelli come il metodo di fabbrica e l'iniezione di dipendenza sono sovrapposti.)

Quindi, se dovessimo creare una capacità di override per la statica senza cambiare nient'altro, avremmo difficoltà a ordinare le sostituzioni. Sarebbe difficile definire un contesto limitato per l'applicabilità dell'override, quindi otterresti l'override a livello globale piuttosto che più localmente come con gli oggetti. Non esiste alcun oggetto di istanza per cambiare il comportamento. Quindi, se qualcuno invocasse il metodo statico che era capitato di essere sostituito da un'altra classe, l'override dovrebbe ottenere il controllo o no? Se sono presenti più sostituzioni, chi ottiene il controllo per primo? secondo? Con l'override dell'oggetto di istanza, tutte queste domande hanno risposte significative e ben motivate, ma con la statica no.

Le sostituzioni per la statica sarebbero sostanzialmente caotiche e cose del genere sono state già fatte in precedenza.

Ad esempio, Mac OS System 7 e precedenti utilizzavano un meccanismo di patch trap per estendere il sistema ottenendo il controllo delle chiamate di sistema effettuate dall'applicazione prima del sistema operativo. Si potrebbe pensare alla tabella delle patch delle chiamate di sistema come a una matrice di puntatori a funzioni, proprio come una vtable per gli oggetti di esempio, tranne per il fatto che si trattava di una singola tabella globale.

Ciò ha causato un dolore indicibile ai programmatori a causa della natura non ordinata del patch trap. Chiunque fosse riuscito a rattoppare l'ultima trappola in pratica ha vinto, anche se non voleva. Ogni patcher della trap catturerebbe il valore trap precedente per una sorta di capacità di chiamata parent, che era estremamente fragile. Rimuovendo una patch trap, dire che quando non si aveva più bisogno di sapere di una chiamata di sistema era considerata una forma errata poiché non si disponevano delle informazioni necessarie per rimuovere la patch (se lo si facesse, si sbloccherebbe anche qualsiasi altra patch seguita voi).

Questo non vuol dire che sarebbe impossibile creare un meccanismo per l'override della statica, ma ciò che probabilmente preferirei fare è invece trasformare i campi statici e i metodi statici in campi di istanze e metodi di istanza di metaclasse, in modo che l'oggetto normale sarebbero quindi applicabili le tecniche di orientamento. Si noti che esistono anche sistemi che fanno questo: CSE 341: classi e metaclassi Smalltalk ; Vedi anche: Qual è l'equivalente Smalltalk dell'elettricità statica di Java?


Sto cercando di dire che dovresti fare qualche seria progettazione delle caratteristiche del linguaggio per farlo funzionare anche ragionevolmente bene. Per un esempio, è stato adottato un approccio ingenuo, che ha zoppicato, ma è stato molto problematico e probabilmente (cioè direi che) sia stato difettoso dal punto di vista architettonico fornendo un'astrazione incompleta e difficile da usare.

Quando hai finito di progettare la funzionalità di sostituzione statica per funzionare correttamente, potresti aver appena inventato una qualche forma di metaclasse, che è un'estensione naturale di OOP in / per metodi basati su classi. Quindi, non c'è motivo di non farlo - e alcune lingue lo fanno davvero. Forse è solo un po 'più un requisito marginale che un certo numero di lingue scelgono di non farlo.


Se sto capendo correttamente: non è una questione se teoricamente tu vorresti o dovresti voler fare qualcosa del genere, ma più che a livello di programmazione, la sua attuazione sarebbe difficile e non necessariamente utile?
PixelArtDragon,

@Garan, vedi il mio addendum.
Erik Eidt,

1
Non compro l'argomento ordinamento; ottieni il metodo fornito dalla classe su cui hai chiamato il metodo (o la prima superclasse che fornisce un metodo in base alla linearizzazione scelta dalla tua lingua).
Hobbs,

1
@PierreArlaud: Ma this.instanceMethod()ha una risoluzione dinamica, quindi se self.staticMethod()ha la stessa risoluzione, vale a dire dinamica, non sarebbe più un metodo statico.
Jörg W Mittag,

1
è necessario aggiungere la semantica prioritaria per i metodi statici. Un'ipotetica metaclasse Java + non ha bisogno di aggiungere nulla! Basta creare oggetti di classe e i metodi statici diventano quindi metodi di istanza regolari. Non devi aggiungere qualcosa al linguaggio, perché usi solo concetti che sono già lì: oggetti, classi e metodi di istanza. Come bonus, puoi effettivamente rimuovere i metodi statici dalla lingua! Sei in grado di aggiungere una funzionalità rimuovendo un concetto e quindi la complessità dalla lingua!
Jörg W Mittag,

9

L'override dipende dall'invio virtuale: si utilizza il tipo di runtime del thisparametro per decidere quale metodo chiamare. Un metodo statico non ha thisparametri, quindi non c'è nulla su cui inviare.

Alcuni linguaggi, in particolare Delphi e Python, hanno un ambito "intermedio" che consente questo: metodi di classe. Un metodo di classe non è un normale metodo di istanza, ma non è neanche statico; riceve un selfparametro (in modo divertente, entrambe le lingue chiamano il thisparametro self) che è un riferimento al tipo di oggetto stesso, piuttosto che a un'istanza di quel tipo. Con quel valore, ora hai un tipo disponibile su cui effettuare l'invio virtuale.

Sfortunatamente, né la JVM né il CLR hanno nulla di simile.


Sono i linguaggi che usano selfche hanno classi come oggetti reali, istanziati con metodi scavalcabili - Smalltalk ovviamente, Ruby, Dart, Objective C, Self, Python? Rispetto ai linguaggi che prendono dopo il C ++, dove le classi non sono oggetti di prima classe, anche se c'è qualche accesso di riflessione?
Jerry101,

Giusto per essere chiari, stai dicendo che in Python o Delphi puoi scavalcare i metodi di classe?
Erik Eidt,

@ErikEidt In Python, praticamente tutto è superabile. In Delphi, un metodo di classe può essere esplicitamente dichiarato virtuale quindi sovrascritto in una classe discendente, proprio come i metodi di istanza.
Mason Wheeler,

Quindi, queste lingue stanno fornendo una sorta di nozione di metaclasse, no?
Erik Eidt,

1
@ErikEidt Python ha metaclass "vere". In Delphi, un riferimento di classe è un tipo di dati speciale, non una classe in sé e per sé.
Mason Wheeler,

3

Tu chiedi

Perché le funzioni statiche non dovrebbero essere sostituibili?

Chiedo

Perché le funzioni che si desidera ignorare dovrebbero essere statiche?

Alcune lingue ti costringono ad iniziare lo spettacolo con un metodo statico. Ma dopo puoi davvero risolvere molti problemi senza ulteriori metodi statici.

Ad alcune persone piace usare metodi statici ogni volta che non c'è dipendenza dallo stato in un oggetto. Ad alcune persone piace usare metodi statici per la costruzione di altri oggetti. Ad alcune persone piace evitare il più possibile metodi statici.

Nessuna di queste persone ha torto.

Se è necessario che sia sostituibile, basta interrompere l'etichettatura statica. Nulla si spezzerà perché hai un oggetto apolide che vola in giro.


Se il linguaggio supporta le strutture, un tipo di unità può essere una struttura di dimensioni zero, che viene passata al metodo di immissione logica. La creazione di quell'oggetto è un dettaglio di implementazione. E se iniziamo a parlare dei dettagli di implementazione come la vera verità, allora penso che anche tu debba considerare che in un'implementazione del mondo reale, un tipo di dimensione zero non deve essere effettivamente istanziato (perché non sono necessarie istruzioni per caricare o memorizzalo).
Theodoros Chatzigiannakis,

E se stai parlando ancora di più "dietro le quinte" (ad esempio a livello di eseguibile), gli argomenti di ambiente potrebbero essere definiti come un tipo nella lingua e il punto di ingresso "reale" del programma potrebbe essere un metodo di istanza di quel tipo, agendo su qualunque istanza è stata passata al programma dal caricatore del sistema operativo.
Theodoros Chatzigiannakis,

Penso che dipenda da come lo guardi. Ad esempio, quando viene avviato un programma, il sistema operativo lo carica in memoria e quindi passa all'indirizzo in cui risiede l'implementazione individuale del programma del punto di ingresso binario (ad esempio il _start()simbolo su Linux). In questo esempio, l'eseguibile è l'oggetto (ha la sua "tabella di invio" e tutto il resto) e il punto di ingresso è l'operazione spedita dinamicamente. Quindi, il punto di ingresso di un programma è intrinsecamente virtuale se visto dall'esterno e può essere facilmente reso virtuale anche se visto dall'interno.
Theodoros Chatzigiannakis,

1
"Nulla si spezzerà perché hai un oggetto apolide che vola in giro." ++++++
RubberDuck

@TheodorosChatzigiannakis fai una forte discussione. se non altro mi hai convinto che la linea non ispira la linea di pensiero che intendevo. Stavo davvero cercando di dare alle persone il permesso di scrollarsi di dosso alcune delle pratiche più abituali che associano alla cieca con metodi statici. Quindi ho aggiornato. Pensieri?
candied_orange

2

Perché i metodi statici non dovrebbero essere sostituibili?

Non è una questione di "dovrebbe".

"Sostituzione" significa "invio dinamico ". "Metodo statico" significa "spedizione statica". Se qualcosa è statico, non può essere ignorato. Se qualcosa può essere ignorato, non è statico.

La tua domanda è piuttosto simile alla domanda: "Perché i tricicli non dovrebbero essere in grado di avere quattro ruote?" La definizione di "triciclo" è che ha tre ruote. Se è un triciclo, non può avere quattro ruote, se ha quattro ruote, non può essere un triciclo. Allo stesso modo, la definizione di "metodo statico" è che viene inviato staticamente. Se è un metodo statico, non può essere inviato in modo dinamico, se può essere inviato in modo dinamico, non può essere un metodo statico.

Naturalmente, è perfettamente possibile avere metodi di classe che possono essere sovrascritti. Oppure, potresti avere un linguaggio come Ruby, in cui le classi sono oggetti come qualsiasi altro oggetto e quindi possono avere metodi di istanza, il che elimina completamente la necessità di metodi di classe. (Ruby ha solo un tipo di metodi: metodi di istanza. Non ha metodi di classe, metodi statici, costruttori, funzioni o procedure.)


1
"Per definizione" Ben messo.
candied_orange

1

pertanto le funzioni statiche in C # non possono essere virtuali o astratte

In C #, chiami sempre membri statici usando la classe, ad esempio BaseClass.StaticMethod()no baseObject.StaticMethod(). Quindi alla fine, se hai ChildClassereditato da BaseClasse childObjectun'istanza di ChildClass, non sarai in grado di chiamare il tuo metodo statico da childObject. Dovrai sempre usare esplicitamente la classe reale, quindi un static virtualsenso non ha senso.

Quello che puoi fare è ridefinire lo stesso staticmetodo nella tua classe figlio e usare la newparola chiave.

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

Se fosse possibile chiamare baseObject.StaticMethod(), allora la tua domanda avrebbe senso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.