Perché le interfacce richiedono metodi sui membri?

8

... Poiché questo ci obbliga a creare getter e setter, che in pratica sono spesso totalmente estranei? Esiste un buon motivo per la progettazione del linguaggio, perché le interfacce nella maggior parte delle lingue (tutte?) Non consentono ai campi membri di soddisfare le specifiche dell'interfaccia?

Nella maggior parte delle lingue esistenti, i membri sono trattati in modo fondamentalmente diverso dai metodi, ma deve essere così? In un linguaggio teorico, non potremmo avere un'interfaccia che (a) specifica un metodo zero args come getter, ma ha accettato un membro leggibile come implementatori, e (b) specifica un metodo single-arg come un setter, ma accettata un campo membro scrivibile come suo implementatore, dato un linguaggio che supportava la specifica del controllo di accesso diretto sulle variabili quando vengono dichiarate? Un linguaggio prototipale che consente l'ereditarietà multipla (per affrontare il punto molto valido che alcuni intervistati hanno fatto), sarebbe l'ideale per qualcosa di simile.

Per favore informami se esiste già qualcosa di simile.

    
posta Engineer 06.08.2011 - 12:36
fonte

11 risposte

3

Obviously in most extant languages, members are treated fundamentally differently from methods, but does this HAVE to be the case?

Non per nessuna ragione teoricamente fondata. Penso che i motivi per cui non lo vedi spesso sono pratici:

Il primo è il modo in cui gestisci queste cose in fase di runtime. In Java o C ++, un'assegnazione a un membro pubblico tipizzato come primitivo come foo.x = 5 è facile da gestire: roba 5 in qualche registro, capire dove nella memoria il x membro di foo vive e scarica il registrati lì. In una lingua come C # in cui classi possono incapsulare compiti di intercettazione per i membri o calcola il modo in cui i membri valuteranno , il compilatore non avrà alcuna conoscenza a priori se l'implementazione di una classe resa disponibile al runtime utilizzi tale funzione. Ciò significa che tutti gli incarichi di membro pubblico e le valutazioni dei membri pubblici devono essere rimandati all'implementazione. * Le operazioni che comportano un sacco di accesso a questi membri finiscono col subire un colpo di performance perché fanno costantemente chiamate subroutine che potrebbero essere gestite più velocemente con regolarità incarichi in linea. Lasciare la valutazione e l'incarico separati dai getter e setter dà allo sviluppatore il controllo sul modo in cui vengono gestiti in una determinata situazione.

Il secondo è l'età. Con la possibile eccezione di F #, nessuna delle lingue in uso molto ampio oggi è più giovane di un decennio, e anche recentemente come allora, i cicli aggiuntivi necessari per rendere possibile la valutazione e l'assegnazione intercettate potrebbero non essere stati accettabili.

Terzo è l'inerzia. Penso che in qualche modo ci sia ancora qualche stigma associato all'assegnazione diretta ai membri pubblici, anche se il linguaggio consente alla classe di manipolarli e la gente pensa ancora in termini di getter e setter che stanno meglio. Ciò si esaurirà con il tempo, proprio come ha fatto con tutte le persone che hanno avuto il naso in aria su persone che hanno scritto programmi in linguaggi di livello superiore invece di assemblare.

* C'è un compromesso che viene fornito con questo: un linguaggio deve andare intero con il rinvio delle valutazioni / assegnazioni alla classe o il balk in fase di esecuzione se il codice che utilizza le eval in-line / assegnazioni per una data classe prova a collegarsi con un'implementazione che li intercetta. Personalmente, penso che quest'ultima sia una cattiva idea perché costringe la ricompilazione del codice che utilizza un'API che è invariata.

    
risposta data 06.08.2011 - 17:36
fonte
10

In most extant languages, member [fields?] are treated fundamentally differently from methods, but does this HAVE to be the case?

Sì, lo è.

I campi membri sono dati per istanza. I dati devono effettivamente esistere da qualche parte nella memoria per ogni istanza: deve esserci un indirizzo o una sequenza di indirizzi riservati. In Java, .NET e molti altri linguaggi OO, questi dati vanno in pila.

I metodi sono dati per classe. Sono indicatori di un vtable. Ecco come i metodi ottengono la spedizione virtuale. Esiste solo un'istanza allocata, per classe.

Un'interfaccia è essenzialmente un tipo speciale di classe che solo contiene metodi, nessun dato di istanza. Fa parte della definizione dell'interfaccia. Se contenesse qualche dato, non sarebbe più un'interfaccia, sarebbe una classe astratta. Entrambe queste scelte vanno bene, ovviamente, a seconda del tipo di modello di progettazione che si sta tentando di implementare. Ma se hai aggiunto dati di istanza alle interfacce, porteresti via tutto ciò che rende loro interfacce.

OK, quindi perché un metodo di interfaccia non può puntare a un campo membro? Perché non c'è niente a livello di classe a cui puntare. Ancora una volta, un'interfaccia non è altro che una tabella dei metodi. Il compilatore può generare solo una tabella e ogni metodo in esso deve puntare allo stesso indirizzo di memoria. Pertanto, è impossibile che i metodi di interfaccia puntino ai campi delle classi, poiché il campo della classe è un indirizzo diverso per ogni istanza.

Forse il compilatore potrebbe comunque accettarlo e generare un metodo getter per l'interfaccia a cui puntare, ma in sostanza diventa una proprietà . Se vuoi sapere perché Java non ha proprietà, beh ... questa è una storia davvero lunga e noiosa che preferirei non approfondire qui. Ma se sei interessato, potresti voler dare un'occhiata ad altri linguaggi OO che fanno implementano proprietà, come C # o Delphi. La sintassi è davvero semplice:

public interface IFoo
{
    int Bar { get; }
}

public class Foo : IFoo
{
    public int Bar { get; set; }
}

Sì, è tutto lì. Importante: questi sono campi non , la "int Bar" nella classe Foo è un Proprietà auto , e il compilatore sta facendo esattamente ciò che ho appena descritto sopra: genera automaticamente getter e setter (oltre al campo del membro di supporto).

Quindi per ricapitolare la risposta alla tua domanda:

  • Le interfacce richiedono metodi perché il loro intero scopo è non di contenere alcun dato;
  • La ragione per cui non esiste uno zucchero sintattico per rendere più semplice per una classe l'implementazione di un'interfaccia è semplicemente che i progettisti Java non lo faranno - e a questo punto nel tempo, la compatibilità con le versioni precedenti è un problema. I progettisti di linguaggi fanno questo tipo di scelte; dovrai solo gestirli o cambiare.
risposta data 06.08.2011 - 16:30
fonte
9

questo è un risultato dell'hiding di informazioni, ovvero quei membri non dovrebbero essere visibili come campi ma come proprietà che possono o meno essere memorizzate / memorizzate localmente

un esempio è un codice hash che cambia quando l'oggetto cambia, quindi è meglio calcolarlo quando viene richiesto e non ogni volta che l'oggetto cambia

oltre alla maggior parte delle lingue sono ereditarietà singola + multi-implements e permettendo ai campi di essere parte delle interfacce ti metti in territorio multi-ereditario (l'unica cosa che manca è l'implementazione predefinita delle funzioni) che è un campo minato di casi limite per avere ragione

    
risposta data 06.08.2011 - 12:52
fonte
6

Le variabili membro sono l'implementazione della classe, non l'interfaccia. Potresti voler cambiare l'implementazione, quindi alle altre classi non dovrebbe essere consentito di fare riferimento direttamente a tale implementazione.

Considera una classe con i seguenti metodi: sintassi C ++, ma non è importante ...

class example
{
  public:
    virtual void Set_Mode (int p_mode);
    virtual int  Mode () const;
};

Sembra abbastanza ovvio che ci sarà una variabile membro chiamata mode o m_mode o simile che memorizza direttamente quel valore di modalità, facendo praticamente ciò che una "proprietà" farebbe in alcune lingue - ma non è necessariamente vero. Esistono molti modi diversi per gestire questo problema. Ad esempio, il metodo Set_Mode potrebbe ...

  1. Identifica la modalità utilizzando un'istruzione switch.
  2. Crea un'istanza di una sottoclasse di qualche classe interna, a seconda del valore della modalità.
  3. Archivia un puntatore a quell'istanza come variabile membro.

Fatto ciò, molti altri metodi della classe di esempio potrebbero chiamare i metodi appropriati di quella classe interna attraverso il puntatore, ottenendo un comportamento specifico della modalità senza dover controllare quale sia la modalità corrente.

Il punto qui è meno che ci sia più di un modo possibile per implementare questo tipo di cose, e molto altro ancora che a un certo momento potresti cambiare idea. Forse hai iniziato con una semplice variabile, ma tutte le istruzioni switch per identificare la modalità in ogni metodo stanno diventando un problema. O forse hai iniziato con la cosa dell'istanza-puntatore, ma si è rivelato troppo pesante per la tua situazione.

Si scopre che cose del genere possono accadere per qualsiasi dato membro. Potrebbe essere necessario modificare le unità in cui è memorizzato un valore da miglia a chilometri, oppure potresti scoprire che l'enumerazione univoca del tuo caso non può più identificare in modo univoco tutti i casi senza considerare alcune informazioni aggiuntive o altro.

Questo può succedere anche per i metodi - alcuni metodi sono puramente per uso interno, dipendono dall'implementazione e dovrebbero essere privati. Ma molti metodi fanno parte dell'interfaccia e non dovranno essere rinominati o qualsiasi altra cosa solo perché l'implementazione interna della classe è stata sostituita.

Ad ogni modo, se la tua implementazione è esposta, un altro codice inizierà inevitabilmente a dipendere da esso e sarai bloccato nel mantenere tale implementazione. Se la tua implementazione è nascosta da un altro codice, quella situazione non può accadere.

Il blocco dell'accesso ai dettagli di implementazione è chiamato "nascondimento dei dati".

    
risposta data 06.08.2011 - 13:25
fonte
3

Come altri hanno notato, un'interfaccia descrive cosa fa una classe, non come lo fa. È essenzialmente un contratto che l'ereditarietà delle classi deve rispettare.

Quello che descrivi è leggermente diverso - una classe che fornisce un'implementazione alcuni ma forse non tutti, come una forma di riutilizzo. Questi sono generalmente noti come classi astratte e sono supportati in un certo numero di lingue (ad esempio C ++ e Java). Queste classi non possono essere istanziate di per sé, possono essere ereditate solo da classi concrete (o ulteriormente astratte).

Come già notato, le classi astratte tendono a essere di maggior valore nelle lingue che supportano l'ereditarietà multipla, in quanto possono essere utilizzate per fornire parti di funzionalità aggiuntive a una classe più ampia. Questo è spesso considerato un cattivo stile, tuttavia, in genere una relazione di aggregazione ("ha-a") sarebbe più appropriata

    
risposta data 06.08.2011 - 13:41
fonte
1

Please inform me if something like this already exists.

Le proprietà Objective-C forniscono questo tipo di funzionalità con i suoi accessori di accesso sintetizzati. Una proprietà Objective-C è essenzialmente solo una promessa che una classe fornisce accessor, in genere nella forma foo per il getter e setFoo: per il setter. La dichiarazione di proprietà specifica alcuni attributi degli accessori relativi alla gestione della memoria e ai comportamenti di threading. C'è anche una direttiva @synthesize che dice al compilatore di generare accessors e anche la corrispondente variabile di istanza, così non devi preoccuparti di scrivere getter e setter o dichiarare un ivar per ogni proprietà. C'è anche dello zucchero sintattico che fa apparire l'accesso alle proprietà come accedere ai campi di una struttura, ma in realtà porta a chiamare i metodi di accesso alle proprietà.

Il risultato di tutto ciò è che posso dichiarare una classe con proprietà:

@interface MyClass : NSObject
{}
@property (assign) int foo;
@property (copy) NSString *bar;
@end

e poi posso scrivere codice che sembra che acceda direttamente a ivars, ma in realtà usa metodi accessor:

MyClass *myObject = [[MyClass alloc] init];  // create myObject

// setters
myObject.foo = 127;                 // same as [myObject setFoo:127];
myObject.bar = @"Hello, world!";    // same as [myObject setBar:@"Hello, world!"];

// getters
int i = myObject.foo                // same as [myObject foo];
NSString *s = myObject.bar          // same as [myObject bar];

È già stato detto che gli accessor ti consentono di separare l'interfaccia dall'implementazione e il motivo per cui vuoi farlo è quello di preservare la tua capacità di modificare l'implementazione in futuro senza influire sul codice client. Lo svantaggio è che devi essere abbastanza disciplinato per scrivere e usare gli accessor solo per mantenere aperte le tue future opzioni. Objective-C rende la generazione e l'utilizzo di accessor facili come usare direttamente gli ivars. Infatti, poiché si occupano di gran parte del lavoro di gestione della memoria, l'uso delle proprietà è molto più semplice dell'accesso diretto a ivars. E quando arriverà il momento di voler cambiare l'implementazione, puoi sempre fornire i tuoi accessors invece di lasciare che il compilatore li generi per te.

    
risposta data 06.08.2011 - 16:54
fonte
1

Why do interfaces require methods over members?

...As this forces us to create getters and setters, which in practice are often totally extraneous? Is there any good language-design reason why interfaces in most (all?) languages do not allow members to help fulfill the interface spec?

"Più" lingue? Quali lingue hai studiato? Nei linguaggi dinamici non ci sono interfacce. Ci sono solo tre o quattro linguaggi OO tipizzati staticamente con qualsiasi popolarità: Java, C #, Objective-C e C ++. C ++ non ha interfacce; utilizziamo invece classi astratte e una classe astratta può contenere un membro di dati pubblici. C # e Objective-C supportano entrambe le proprietà, quindi non c'è bisogno di getter e setter in quelle lingue. Questo lascia solo Java. Presumibilmente l'unica ragione per cui Java non supporta le proprietà è che è stato difficile introdurle in un modo compatibile con le versioni precedenti.

    
risposta data 06.08.2011 - 18:45
fonte
1

Alcune delle risposte menzionano "occultamento delle informazioni" e "dettagli di implementazione" poiché i metodi dell'interfaccia della motivazione sono preferiti rispetto ai campi. Penso che la ragione principale sia più semplice di quella.

Le interfacce sono una funzione linguistica che ti consente di scambiare il comportamento facendo sì che più classi implementino un'interfaccia. Il comportamento di una classe è definito dai suoi metodi. I campi, d'altra parte, riflettono solo lo stato di un oggetto.

Naturalmente, i campi o le proprietà di una classe possono essere parte del comportamento, ma non sono la parte importante del contratto definita dall'interfaccia; sono solo un risultato. Questo è il motivo per es. C # consente di definire le proprietà in un'interfaccia e in Java si definisce un getter nell'interfaccia. È qui che entra in gioco un po 'di informazioni nascoste, ma non è il punto principale.

Come ho detto prima, le interfacce ti consentono di scambiare il comportamento, ma diamo un'occhiata alla parte di swapping. Un'interfaccia ti consente di modificare l'implementazione sottostante creando una nuova classe di implementazione. Quindi avere un'interfaccia composta da campi non ha molto senso, perché non si può cambiare molto sull'implementazione dei campi.

L'unica ragione per cui vuoi creare un'interfaccia solo campo è quando vuoi dire che alcuni classe sono un certo tipo di oggetto. Ho enfatizzato la parte 'is a', perché dovresti usare l'ereditarietà piuttosto che le interfacce.

Nota a margine: so che la composizione che preferisce rispetto all'ereditarietà e alla composizione di solito può essere raggiunta solo usando le interfacce, ma usare le interfacce solo per "taggare" una classe con alcune proprietà è altrettanto brutta. L'ereditarietà è ottima se utilizzata in modo appropriato e, finché si attacca al Principio di Responsabilità Unica, non si dovrebbero avere problemi.

Voglio dire, non ho mai lavorato con una libreria di controllo dell'interfaccia utente, ad esempio, che non utilizza l'ereditarietà. L'unica area in cui le interfacce entrano in gioco qui è nel provisioning dei dati, ad es. un ITableDataAdapter per associare dati a un controllo tabella. Questo è esattamente il tipo di interfaccia delle cose, per avere comportamento intercambiabile .

    
risposta data 07.08.2011 - 12:19
fonte
0

In Java la ragione è che le interfacce consentono solo di specificare i metodi .

Quindi se vuoi qualcosa con un campo nel contratto di interfaccia, devi farlo come metodo getter e setter.

Vedi anche questa domanda: Quando Getter e setter sono giustificati

    
risposta data 06.08.2011 - 12:51
fonte
-1

Il punto di un'interfaccia è specificare le funzioni che devi implementare per rivendicare il supporto di questa interfaccia.

    
risposta data 06.08.2011 - 12:52
fonte
-1

I membri non sono consentiti nelle interfacce perché in tal caso tali linguaggi dovrebbero affrontare l'orrendo male che è, l'ereditarietà multipla.

    
risposta data 06.08.2011 - 14:18
fonte

Leggi altre domande sui tag