2011-12-28 15 views
5

Wikipedia definisce virtual methods come:I metodi nei paradigmi orientati agli oggetti possono essere sovrascritti dai metodi con la stessa firma nelle classi ereditanti. Le variabili tuttavia non possono. Perché?

Nella programmazione orientata agli oggetti, una funzione virtuale o un metodo virtuale è una funzione o metodo cui comportamento può essere ignorato all'interno di una sottoclasse da una funzione con la stessa firma [prestazione un comportamento polimorfico].

Secondo la definizione, ogni metodo non statico in Java è di default virtuale tranne metodi finali e privati. Il metodo che non può essere ereditato per il comportamento polimorfico è non un metodo virtuale.

I metodi statici in Java non possono mai essere sovrascritti; quindi, non ha senso dichiarare un metodo statico come finale in Java perché i metodi statici stessi si comportano proprio come i metodi finali. Possono semplicemente essere nascosti nelle sottoclassi con i metodi con la stessa firma. Evidentemente è così perché i metodi statici non possono mai avere un comportamento polimorfico: un metodo che viene sovrascritto deve ottenere il polimorfismo, che non è il caso dei metodi statici.

Dal paragrafo precedente, una conclusione importante può essere guidata. Tutti i metodi in C++ sono di default statici perché nessun metodo in C++ può comportarsi in modo polimorfico finché e a meno che non siano esplicitamente dichiarati virtuali nella super classe. Al contrario, tutti i metodi in Java tranne i metodi definitivi, statici e privati ​​sono di default virtuali perché hanno un comportamento polimorfico di default (non è necessario dichiarare esplicitamente metodi come virtuali in Java e, di conseguenza, Java non ha parole chiave come "virtuale").

Ora, dimostriamo che le variabili di istanza (anche statiche) non possono comportarsi in modo polimorfico dal seguente semplice esempio in Java.

class Super 
{ 
    public int a=5; 
    public int show() 
    { 
     System.out.print("Super method called a = "); 
     return a; 
    } 
} 

final class Child extends Super 
{ 
    public int a=6; 

    @Override 
    public int show() 
    { 
     System.out.print("Child method called a = "); 
     return a; 
    } 
} 

final public class Main 
{ 
    public static void main(String...args) 
    { 
     Super s = new Child(); 
     Child c = new Child(); 

     System.out.println("s.a = "+s.a); 
     System.out.println("c.a = "+c.a);   

     System.out.println(s.show()); 
     System.out.println(c.show()); 
    } 
} 

L'uscita prodotto dal frammento di codice sopra è il seguente.

 
s.a = 5 
c.a = 6 
Child method called a = 6 
Child method called a = 6 

In questo esempio, entrambe le chiamate s.show() e c.show() pervenute al metodo show() attraverso le variabili di tipo Super e di tipo Child, rispettivamente, richiamare il metodo show() nella classe Child. Ciò significa che il metodo show() nella classe Child sovrascrive il metodo show() nella classe Super poiché entrambi hanno la stessa firma.

Questo, tuttavia, non può essere applicato alla variabile di istanza a dichiarata in entrambe le classi.In questo caso, sarebbe s.a consultare a nella classe Super e visualizzazione 5 e c.a rammenta le a nella classe Child e potrai 6 significa che a nelle Child classe soli pelli (e non ridefinisce come è accaduto a non statico metodi) a nella classe Super.

Dopo questa lunga discussione, c'è solo una domanda. Perché le variabili di istanza (e anche il resto) non sono sovrascritte? Quali erano le ragioni speciali per attuare un tale meccanismo? Ci sarebbero stati vantaggi o svantaggi se fossero stati ignorati?

+1

I metodi non virtuali C++ non sono statici. sono metodi di istanza che non possono essere sovrascritti. Sono simili ai metodi finali in Java: metodi di istanza che non possono essere sovrascritti. –

+0

'Secondo il paragrafo precedente, una conclusione importante può essere guidata. Tutti i metodi in C++ (anche in C) sono di default statici perché nessun metodo in C++ può comportarsi in modo polimorfico fino a quando non sono esplicitamente dichiarati virtuali nella base': un metodo statico non può accedere all'oggetto [no 'this' per questi metodi ] mentre una funzione non predefinita [predefinita] in C++ può – amit

+1

I cosa la parola "sovrascritta" non è applicabile alle variabili. Esegui l'override del comportamento, ma non lo stato. È possibile accedere alle variabili dalla sottoclasse, quindi non è necessario "eseguire l'override" (è possibile modificarne il valore e ha lo stesso nome). Inoltre c'è qualcosa chiamato shadowing, quindi se si dichiara nuovamente un'altra variabile con lo stesso nome è un'altra variabile diversa in fase di esecuzione. –

risposta

3

Penso che lo scopo di ignorare sta cambiando la funzionalità senza modificare la firma. Questo è rilevante solo per i metodi: il metodo può avere la stessa firma ma ha un comportamento diverso. I campi hanno solo "firma" che è anche limitata al tipo e al nome. Non hanno un comportamento, quindi nulla può essere ignorato.

Un altro motivo per l'impossibilità di "sovrascrivere" i campi è che i campi sono in genere privati ​​(o dovrebbero essere privati), quindi sono in realtà i dettagli cruenti dell'implementazione della classe. I metodi tuttavia rappresentano l'interfaccia esterna della classe. Questa interfaccia esterna dovrebbe supportare il polimorfismo per semplificare la modifica della funzionalità dei moduli senza modificare le relazioni tra i moduli.

+0

Secondo il principio di sostituzione di Liskov, le firme non devono essere le stesse: il tipo di ritorno deve essere covariante (tipo stesso o derivato) e gli argomenti devono essere controvarianti (tipo stesso o basale) nella firma dei metodi di sovrascrittura. Allo stesso modo, il tipo di campo con lo stesso nome in un discendente dovrebbe essere covariante, quindi può avere senso "sovrascrivere" i campi. – outis

+0

@outis: Il tipo di un campo è analogo * sia * al tipo di ritorno di un getter ('Number x = this.x' è analogo a' Number x = this.getX() ') * e * al parametro di un setter -type ('this.x = (Number) x' è analogo a' this.setX ((Number) x) '). Quindi deve essere invariante. (E questo è vero anche se il campo è 'finale', poiché il costruttore della superclasse è ciò che lo imposterà in quel caso.) – ruakh

2

C'è un altro uso del termine "statico" quando ci si riferisce alla risoluzione del nome: la risoluzione statica è quando il tipo dichiarato di una variabile viene utilizzato per risolvere il nome, dinamico quando viene utilizzato il tipo di dati memorizzati nella variabile. La risoluzione statica può essere eseguita in fase di compilazione, mentre la risoluzione dinamica deve essere eseguita in fase di esecuzione. La risoluzione dei membri virtuali (che è una forma di risoluzione dinamica) richiede un livello aggiuntivo di riferimento indiretto, una ricerca tabella. Consentire i campi "virtuali" comporterebbe un costo di runtime.

Una volta eliminata la risoluzione statica per tutti i membri, non ha molto senso avere variabili tipizzate staticamente. A quel punto, si può anche avere un linguaggio tipizzato dinamicamente, come Python e la moltitudine di varianti Lisp. Quando si eliminano i tipi statici, non ha più senso cercare di parlare di campi secondari che non sovrascrivono (o sovrascrivono) i campi parent, perché non esiste più un tipo di variabile statica da suggerire diversamente.

class Super(object): 
    a=5 
    def show(self): 
     print("Super method called a = ", end='') 
     return self.a 

class Child(Super): 
    a=6 
    def show(self): 
     print("Child method called a = ", end='') 
     return self.a 

# there's no indication that 's' is supposed to be a Super... 
s = Child(); 
c = Child(); 

# so it's no surprise that s.a is Child.a 
print("s.a =", s.a) 
# result: "s.a = 6" 
print("c.a =", c.a) 
# result: "c.a = 6" 
print(s.show()) 
# result: "Child method called a = 6" 
print(c.show()) 
# result: "Child method called a = 6" 

I bit comuni, si deve notare, hanno sia dichiarazioni di tipo che risoluzione dinamica.

(defgeneric show (o)) 

(defclass super() 
    ((a :accessor super-a 
    :initform 5 
    :initarg :a)) 
) 

(defmethod show ((o super)) 
    (list "Super method called a = " 
     (slot-value o 'a)) 
) 

(defclass child (super) 
    ((a :accessor child-a 
    :initform 6 
    :initarg :a)) 
) 

(defmethod show ((o child)) 
    (list "Child method called a = " 
     (slot-value o 'a)) 
) 

(defun test (s c) 
    (declare (type super s)) 
    (declare (type child c)) 
    (list (list "s.a =" (slot-value s 'a)) 
     (list "c.a =" (slot-value c 'a)) 
     (show s) 
     (show c) 
     ) 
) 

(test (make-instance 'child) (make-instance 'child)) 
;; result: '(("s.a =" 6) ("c.a =" 6) ("Child method called a = " 6) ("Child method called a = " 6)) 

In puro OOP (secondo alcuni), gli oggetti non dovrebbero avere campi pubblici, solo i membri del pubblico, in modo che i campi sono stati risolti in modo statico non è un problema.

Tutti i metodi in C++ (anche in C) sono di statico predefinito [...]

No, perché static significa più di "non può essere ignorata" in Java, e don non lo dico affatto in C++. In entrambi, la proprietà essenziale dei metodi statici è che sono metodi di classe, accessibili tramite una classe, piuttosto che un'istanza (sebbene Java consenta l'accesso a entrambi).

2

Guarda il mondo C#. L'uso di variabili di istanza pubbliche è severamente scoraggiato - vedi StyleCop. L'unico modo consigliato è usare proprietà che possono essere sostituite.

Penso che questo sia l'approccio giusto. Basta non usare variabili di istanza pubbliche e usare analogie con le proprietà.

Per quanto riguarda il motivo per cui è così in Java, penso che un certo tipo di stile C++ che è stato adottato in quel momento del punto in cui Java è stato progettato come un modo per facilitare la transizione.

+0

C# è proprio come Java. anche i campi pubblici sono considerati un annuncio practive in Java. C# ha solo zucchero sintattico per accedere alle proprietà usando una sintassi di accesso al campo, e Java usa getter e setter, ma sono la stessa cosa. –

+0

Sicuro.Per essere sinceri, mi aspetto una migliore sintassi per getter e setter in Java nella prossima versione. –

+0

@Jiri Pik La sintassi attuale va bene. Se non ti piace rendere i campi pubblici. Penso che la sintassi C# per le proprietà sia orrenda. –

1

C'è poco da scavalcare in un campo di istanza. Dovresti solo sostituirlo con la stessa cosa: stesso nome, stesso tipo. Potresti sostituire un array con una lunghezza diversa, ma dovresti dare un'occhiata da vicino a tutte le super classi per assicurarti che non stiano facendo ipotesi sulla lunghezza.

I campi non privati ​​sono, come notato da altri, di solito qualcosa da evitare. A volte una classe è più di una struttura C, con campi pubblici e senza metodi. Questa è una buona pratica, ma non ha ancora senso sovrascrivere nulla.

Override è in realtà solo sui metodi, indipendentemente dal linguaggio. Java non ha proprietà come C#, che sono classi (limitate) a sé stante con i metodi da modificare. I campi possono sembrare e agire come proprietà, ma non sono affatto la stessa cosa.

Problemi correlati