Domanda

Questa è in qualche modo una domanda di follow-up a questo domanda .

Supponiamo che io abbia un albero ereditario come segue:

Car -> Ford -> Mustang -> MustangGT

C'è un vantaggio nella definizione delle interfacce per ciascuna di queste classi? Esempio:

ICar -> IFord -> IMustang -> IMustangGT

Vedo che forse altre classi (come Chevy ) vorrebbero implementare Icar o IFord e forse anche IMustang , ma probabilmente non IMustangGT perché è così specifico. Le interfacce sono superflue in questo caso?

Inoltre, penso che qualsiasi classe che vorrebbe implementare IFord vorrebbe sicuramente usare la sua unica eredità ereditando da Ford per non duplicare il codice. Se questo è un dato, quali sono i vantaggi dell'implementazione di IFord ?

È stato utile?

Soluzione

Nella mia esperienza, le interfacce vengono utilizzate al meglio quando si hanno diverse classi, ciascuna delle quali deve rispondere allo stesso metodo o metodi, in modo che possano essere utilizzate in modo intercambiabile con altri codici che verranno scritti sull'interfaccia comune di tali classi. Il miglior uso di un'interfaccia è quando il protocollo è importante ma la logica sottostante può essere diversa per ogni classe. Se altrimenti dovresti duplicare la logica, considera invece le classi astratte o l'ereditarietà delle classi standard.

E in risposta alla prima parte della tua domanda, consiglierei di non creare un'interfaccia per ciascuna delle tue classi. Ciò ingombrerebbe inutilmente la tua struttura di classe. Se trovi che hai bisogno di un'interfaccia puoi sempre aggiungerla in seguito. Spero che questo aiuti!

Adam

Altri suggerimenti

Sono anche d'accordo con la risposta di adamalex che le interfacce dovrebbero essere condivise da classi che dovrebbero rispondere a alcuni metodi.

Se le classi hanno funzionalità simili, ma non sono direttamente correlate tra loro in una relazione ancestrale, un'interfaccia sarebbe un buon modo per aggiungere quella funzione alle classi senza duplicare la funzionalità tra i due. (O hanno implementazioni multiple con solo sottili differenze.)

Mentre stiamo usando un'analogia per auto, un esempio concreto. Diciamo che abbiamo le seguenti classi:

Car -> Ford   -> Escape  -> EscapeHybrid
Car -> Toyota -> Corolla -> CorollaHybrid

Le auto hanno ruote e possono guidare () e sterzare () . Pertanto, tali metodi dovrebbero esistere nella classe Car . (Probabilmente la classe Car sarà una classe astratta.)

Scendendo la linea, otteniamo la distinzione tra Ford e Toyota (probabilmente implementato come differenza nel tipo di emblema sull'auto, di nuovo probabilmente una classe astratta. )

Quindi, finalmente abbiamo una classe Escape e Corolla che sono classi che sono completamente implementate come un'auto.

Ora, come possiamo realizzare un veicolo ibrido ?

Potremmo avere una sottoclasse di Escape che è EscapeHybrid che aggiunge un metodo FordsHybridDrive () e una sottoclasse di Corolla ovvero CorollaHybrid con il metodo ToyotasHybridDrive () . I metodi stanno sostanzialmente facendo la stessa cosa, ma abbiamo metodi diversi. Che schifo. Sembra che possiamo fare di meglio.

Supponiamo che un ibrido abbia un metodo HybridDrive () . Dal momento che non vogliamo finire per avere due diversi tipi di ibridi (in un mondo perfetto), quindi possiamo creare un'interfaccia IHybrid che ha un metodo HybridDrive () .

Quindi, se vogliamo creare una classe EscapeHybrid o CorollaHybrid , tutto ciò che dobbiamo fare è implementare la < code> IHybrid interfaccia .

Per un esempio del mondo reale, diamo un'occhiata a Java. Una classe che può fare un confronto di un oggetto con un altro oggetto implementa l'interfaccia Comparable . Come suggerisce il nome, l'interfaccia dovrebbe essere per una classe comparabile , quindi il nome " Comparable " ;.

Proprio come una questione di interesse, un esempio di auto viene utilizzato nella Interfaces di il Tutorial Java .

Non dovresti implementare nessuna di queste interfacce.

L'eredità della classe descrive che cosa un oggetto è (ad esempio: è identità). Questo va bene, comunque il più delle volte ciò che un oggetto è , è molto meno importante di ciò che fa un oggetto . È qui che entrano in gioco le interfacce.

Un'interfaccia dovrebbe descrivere cosa fa un oggetto ) o come si comporta. Con questo intendo il comportamento e l'insieme di operazioni che hanno senso dato quel comportamento.

Di conseguenza, i nomi di buone interfacce dovrebbero di solito avere la forma IDriveable , IHasWheels e così via. A volte il modo migliore per descrivere questo comportamento è fare riferimento a un altro oggetto ben noto, quindi puoi dire che "agisce come uno di questi" (ad es. IList ) ma IMHO questa forma di denominazione è in minoranza.

Data questa logica, gli scenari in cui ereditarietà dell'interfaccia ha un senso completamente e completamente diversi dagli scenari in cui ereditarietà degli oggetti ha senso - spesso questi scenari non si riferiscono a vicenda a tutti.

Spero che ti aiuti a pensare attraverso le interfacce di cui dovresti effettivamente avere bisogno :-)

Direi solo di creare un'interfaccia per le cose a cui devi fare riferimento. Potresti avere altre classi o funzioni che devono sapere su un'auto, ma con quale frequenza ci sarà qualcosa che deve sapere su un guado?

Non costruire cose che non ti servono. Se risulta che hai bisogno delle interfacce, è un piccolo sforzo per tornare indietro e costruirle.

Inoltre, sul lato pedante, spero che tu non stia effettivamente costruendo qualcosa che assomigli a questa gerarchia. Questo non è ciò per cui l'ereditarietà dovrebbe essere usata.

Crealo solo quando diventa necessario quel livello di funzionalità.

Il codice di factoring è sempre in corso.

Ci sono strumenti disponibili che ti permetteranno di estrarre l'interfaccia se necessario. PER ESEMPIO. http://geekswithblogs.net/ JaySmith / archive / 2008/02/27 / refactoring-visual-studio-estratti-interface.aspx

Crea una ICar e tutto il resto (Make = Ford, Model = Mustang e stuff) come membri di una classe che implementa l'interfaccia.

Potresti voler avere la tua classe Ford e, ad esempio, la classe GM ed entrambi implementare ICar per usare il polimorfismo se non vuoi andare sulla strada del controllo Make == qualunque , va bene al tuo stile.

Ad ogni modo - secondo me quelli sono attributi di un'auto e non viceversa - hai solo bisogno di un'interfaccia perché i metodi sono comuni: Brake, SpeedUp, ecc.

Una Ford può fare cose che altre macchine non possono? Io non la penso così.

Vorrei creare i primi due livelli, ICar e IFord e lasciare solo il secondo livello finché non avrò bisogno di un'interfaccia a quel secondo livello.

Pensa attentamente a come i tuoi oggetti devono interagire tra loro all'interno del tuo dominio problematico e considera se devi avere più di un'implementazione di un particolare concetto astratto. Utilizzare le interfacce per fornire un contratto attorno a un concetto con cui interagiscono altri oggetti.

Nel tuo esempio, suggerirei che Ford è probabilmente un produttore e Mustang è un valore ModelName utilizzato dal produttore Ford, quindi potresti avere qualcosa di più simile a:

IVehichle - > CarImpl, MotorbikeImpl - has-a Manufacturer has-many ModelNames

In questa risposta sulla tra interfaccia e classe , Ho spiegato che:

    L'interfaccia
  • espone ciò che un concetto è (in termini di " cosa è " valido, al momento della compilazione), e viene usato per valori (MyInterface x = ...)
  • La classe
  • espone ciò che fa un concetto (effettivamente eseguito in fase di esecuzione) e viene utilizzato per valori o per oggetti (MyClass x o aMyClass.method ())

Quindi, se hai bisogno di memorizzare in una variabile 'Ford' (nozione di 'valore') diverse sottoclassi di Ford, crea un IFord. Altrimenti, non preoccuparti finché non ne avrai davvero bisogno.

Questo è un criterio: se non viene soddisfatto, IFord è probabilmente inutile.
Se viene soddisfatto, si applicano gli altri criteri esposti nelle risposte precedenti: Se una Ford ha un'API più ricca di un'auto, un IFord è utile ai fini dei polimorfismi. In caso contrario, ICar è sufficiente.

A mio avviso, le interfacce sono uno strumento per far rispettare un requisito secondo cui una classe implementa una certa firma o (come mi piace pensarla) un certo "comportamento". Per me penso che la I maiuscola all'inizio dei miei nomi di interfaccia come pronome personale, e provo a nominare le mie interfacce in modo che possano essere lette in questo modo ... ICanFly, IKnowHowToPersistMyself IAmDisplayable, etc ... Quindi nel tuo esempio , Non creerei un'interfaccia per rispecchiare la firma pubblica completa di alcuna classe specifica. Analizzerei la firma pubblica (il comportamento) e quindi separerei i membri in gruppi logici più piccoli (il più piccolo è il migliore) come (usando il tuo esempio) IMove, IUseFuel, ICarryPassengers, ISteerable, IAccelerate, IDepreciate, ecc ... E quindi applicare quelle interfacce a qualunque altra classe nel mio sistema ne abbia bisogno

In generale, il modo migliore per pensare a questo (e molte domande in OO) è pensare al concetto di un contratto.

Un contratto è definito come un accordo tra due (o più) parti, che stabilisce obblighi specifici che ciascuna parte deve rispettare; in un programma, questo è ciò che i servizi forniranno una classe e ciò che devi fornire alla classe per ottenere i servizi. Un'interfaccia indica un contratto che qualsiasi classe che implementa l'interfaccia deve soddisfare.

Con questo in mente, tuttavia, la tua domanda dipende in qualche modo dalla lingua che stai usando e da cosa vuoi fare.

Dopo molti anni di OO (tipo, oh mio dio, 30 anni) di solito scrivo un'interfaccia per ogni contratto, specialmente in Java, perché rende i test molto più facili: se ho un'interfaccia per la classe, Posso costruire oggetti finti facilmente, quasi banalmente.

Le interfacce devono essere un'API pubblica generica e gli utenti saranno limitati a utilizzare questa API pubblica. A meno che tu non intenda che gli utenti utilizzino i metodi specifici del tipo di IMustangGT , potresti voler limitare la gerarchia dell'interfaccia a ICar e IExpensiveCar .

Eredita solo da interfacce e classi astratte.

Se hai un paio di classi che sono quasi uguali e devi implementare la maggior parte dei metodi, usa e interfaccia in combinazione con l'acquisto dell'altro oggetto.
Se le classi Mustang sono così diverse, non solo creare un'interfaccia ICar, ma anche IMustang.
Quindi la classe Ford e Mustang possono ereditare da ICar e Mustang e MustangGT da ICar e IMustang.

Se si implementa la classe Ford e un metodo è lo stesso di Mustang, acquistare da Mustang:

class Ford{
  public function Foo(){
    ...
    Mustang mustang  = new Mustang();
    return mustang.Foo();
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top