Devo usare un'interfaccia quando solo una classe lo implementerà mai?

209

L'intero punto dell'interfaccia per le classi multiple non è conforme a un insieme di regole e implementazioni?

    
posta Lamin Sanneh 07.08.2012 - 09:38
fonte

15 risposte

183

In senso stretto, no non lo fai, si applica YAGNI . Detto questo, il tempo che dedicherete a creare l'interfaccia è minimo, soprattutto se avete a portata di mano un pratico strumento di generazione del codice che fa la maggior parte del lavoro per voi. Se non sei sicuro se avrai bisogno o meno dell'interfaccia, direi che è meglio sbagliare per supportare la definizione di un'interfaccia.

Inoltre, l'utilizzo di un'interfaccia anche per una singola classe ti fornirà un'altra implementazione fittizia per i test unitari, uno non in produzione. La risposta di Avner Shahar-Kashtan si espande su questo punto.

    
risposta data 07.08.2012 - 09:45
fonte
123

Risponderei che se hai bisogno di un'interfaccia o no non dipende da quante classi lo implementeranno. Le interfacce sono uno strumento per definire contratti tra più sottosistemi della tua applicazione; quindi ciò che conta davvero è come la tua applicazione è suddivisa in sottosistemi. Ci dovrebbero essere interfacce come front-end per sottosistemi incapsulati, non importa quante classi li implementino.

Ecco una utile regola empirica:

  • Se rendi la classe Foo riferita direttamente alla classe BarImpl , ti impegni strongmente a cambiare Foo ogni volta che cambi BarImpl . In pratica li tratti come un'unica unità di codice suddivisa in due classi.
  • Se rendi Foo riferimento all'interfaccia Bar , ti impegni invece a evitare le modifiche a Foo quando cambi BarImpl .

Se definisci le interfacce nei punti chiave della tua applicazione, rifletti attentamente sui metodi che dovrebbero supportare e quali non dovrebbero, e commenta chiaramente le interfacce per descrivere come dovrebbe comportarsi un'implementazione (e come non), la tua applicazione sarà molto più facile da comprendere perché queste interfacce commentate forniranno una sorta di specifica dell'applicazione - una descrizione di come intende comportarsi. Questo rende molto più facile leggere il codice (invece di chiedere "cosa diavolo dovrebbe fare questo codice" puoi chiedere "come fa questo codice a fare quello che dovrebbe fare").

Oltre a tutto questo (o in realtà a causa di esso), le interfacce promuovono la compilazione separata. Poiché le interfacce sono banali da compilare e hanno meno dipendenze rispetto alle loro implementazioni, significa che se si scrive la classe Foo per utilizzare un'interfaccia Bar , di solito è possibile ricompilare BarImpl senza dover ricompilare Foo . Nelle applicazioni di grandi dimensioni questo può far risparmiare molto tempo.

    
risposta data 07.08.2012 - 20:00
fonte
93

Le interfacce sono designate per definire un comportamento, cioè una serie di prototipi di funzioni / metodi. I tipi che implementano l'interfaccia implementeranno tale comportamento, quindi quando gestisci un tipo di questo tipo sai (in parte) che comportamento ha.

Non è necessario definire un'interfaccia se si sa che il comportamento da esso definito verrà utilizzato una sola volta. KISS (mantienilo semplice, stupido)

    
risposta data 07.08.2012 - 09:47
fonte
62

Anche se in teoria non dovresti avere un'interfaccia solo per il gusto di avere un'interfaccia, risposta di Yannis Rizos suggerisce ulteriori complicazioni:

Quando scrivi dei test unitari e usi framework mock come Moq o FakeItEasy (per nominare i due più recenti che ho usato), stai implicitamente creando un'altra classe che implementa l'interfaccia. La ricerca nel codice o nell'analisi statica potrebbe affermare che esiste solo un'implementazione, ma in realtà esiste l'implementazione fittizia interna. Ogni volta che inizi a scrivere mock, scoprirai che l'estrazione delle interfacce ha senso.

Ma aspetta, c'è di più. Esistono più scenari in cui sono presenti implementazioni implicite dell'interfaccia. L'utilizzo dello stack di comunicazione WCF di .NET, ad esempio, genera un proxy per un servizio remoto che, di nuovo, implementa l'interfaccia.

In un ambiente con codice pulito, sono d'accordo con il resto delle risposte qui. Tuttavia, prestate attenzione a qualsiasi framework, pattern o dipendenza che possiate usare nelle interfacce.

    
risposta data 07.08.2012 - 10:02
fonte
19

Sembra che le risposte su entrambi i lati della barriera possano essere riassunte in questo:

Design well, and put interfaces where interfaces are needed.

Come ho notato nella mia risposta alla risposta di Yanni , non penso che tu possa mai avere un duro e regola veloce sulle interfacce. La regola deve essere, per definizione, flessibile. La mia regola sulle interfacce è che un'interfaccia dovrebbe essere utilizzata ovunque tu stia creando un'API. E un'API dovrebbe essere creata ovunque tu stia attraversando il confine da un dominio di responsabilità a un altro.

Per esempio (orribilmente inventato), supponiamo che tu stia creando una classe Car . Nella tua classe, avrai sicuramente bisogno di un livello dell'interfaccia utente. In questo particolare esempio, assume la forma di un IginitionSwitch , SteeringWheel , GearShift , GasPedal e BrakePedal . Poiché questa auto contiene un AutomaticTransmission , non è necessario un ClutchPedal . (E dal momento che questa è una macchina terribile, non c'è A / C, radio o sedile. In effetti, anche le assi del pavimento sono mancanti - devi solo aggrapparti a quel volante e sperare per il meglio!)

Quindi quale di queste classi ha bisogno di un'interfaccia? La risposta potrebbe essere tutte o nessuna, a seconda del progetto.

Potresti avere un'interfaccia simile a questa:

Interface ICabin
    Event IgnitionSwitchTurnedOn()
    Event IgnitionSwitchTurnedOff()
    Event BrakePedalPositionChanged(int percent)
    Event GasPedalPositionChanged(int percent)
    Event GearShiftGearChanged(int gearNum)
    Event SteeringWheelTurned(float degree)
End Interface

A quel punto, il comportamento di tali classi diventa parte di ICabin Interface / API. In questo esempio le classi (se ce ne sono) sono probabilmente semplici, con alcune proprietà e una funzione o due. E quello che stai implicitamente affermando nel tuo progetto è che queste classi esistono unicamente per supportare qualsiasi implementazione concreta di ICabin che hai, e non possono esistere da sole, o sono prive di significato al di fuori del contesto di ICabin.

È la stessa ragione per cui non collaudi i membri privati: esistono solo solo per supportare l'API pubblica, quindi il loro comportamento dovrebbe essere verificato testando l'API.

Quindi se la tua classe esiste esclusivamente per supportare un'altra classe, e concettualmente la vedi come veramente che ha il proprio dominio, allora va bene saltare l'interfaccia. Ma se la tua classe è abbastanza importante da considerarla cresciuta abbastanza da avere il proprio dominio, allora vai avanti e dagli un'interfaccia.

Modifica

Frequentemente (inclusa in questa risposta) leggerai cose come 'dominio', 'dipendenza' (frequentemente accoppiata con 'iniezione') che non significano nulla per te quando inizi a programmare (sono sicuri non significava niente per me). Per dominio, significa esattamente come suona:

The territory over which dominion or authority is exerted; the possessions of a sovereign or commonwealth, or the like. Also used figuratively. [WordNet sense 2] [1913 Webster]

Nei termini del mio esempio - prendiamo in considerazione IgnitionSwitch . In una macchina di lavorazione della carne, l'interruttore di accensione è responsabile di:

  1. Autenticare (non identificare) l'utente (hanno bisogno della chiave corretta)
  2. Fornire corrente all'avviatore in modo che possa effettivamente avviare la macchina
  3. Fornire corrente al sistema di accensione in modo che possa continuare a funzionare
  4. Spegnere la corrente in modo che l'auto si fermi.
  5. A seconda di come la vedi, nella maggior parte (tutte?) le auto più recenti c'è un interruttore che impedisce alla chiave di essere rimossa dall'accensione mentre la trasmissione è fuori da Park, quindi potrebbe far parte del suo dominio. (In realtà significa che ho bisogno di ripensare e ridisegnare il mio sistema ...)

Queste proprietà costituiscono il dominio di IgnitionSwitch o, in altre parole, di ciò che ne sa e ne è responsabile.

Il IgnitionSwitch non è responsabile per GasPedal . L'interruttore di accensione è completamente ignaro del pedale del gas in ogni modo. Entrambi funzionano completamente indipendenti l'uno dall'altro (anche se una macchina sarebbe abbastanza inutile senza entrambi!).

Come ho affermato in origine, dipende dal tuo progetto. Puoi progettare un IgnitionSwitch con due valori: On ( True ) e Off ( False ). Oppure puoi progettarlo per autenticare la chiave fornita e una serie di altre azioni. Questa è la parte difficile dell'essere uno sviluppatore è decidere dove disegnare le linee nella sabbia - e onestamente il più delle volte è completamente relativo. Quelle linee nella sabbia sono importanti però - è qui che si trova la tua API, e quindi dove dovrebbero essere le tue interfacce.

    
risposta data 08.08.2012 - 13:41
fonte
19

No, non ne hai bisogno e lo considero un anti-pattern per creare automaticamente interfacce per ogni riferimento di classe.

C'è un costo reale per rendere Foo / FooImpl per tutto. L'IDE può creare l'interfaccia / implementazione gratuitamente, ma quando si naviga nel codice, si ha il carico cognitivo aggiuntivo da F3 / F12 in foo.doSomething() che porta alla firma dell'interfaccia e non all'effettiva implementazione che si desidera. Inoltre hai la confusione di due file invece di uno per ogni cosa.

Quindi dovresti farlo solo quando ne hai effettivamente bisogno per qualcosa.

Ora affrontando i controsensi:

Ho bisogno di interfacce per i framework di injection Dependency

Le interfacce per supportare i framework sono legacy. In Java, le interfacce erano un requisito per i proxy dinamici, pre-CGLIB. Oggi, di solito non ne hai bisogno. È considerato un progresso e un vantaggio per la produttività degli sviluppatori che non ti servono più in EJB3, Spring ecc.

Mi servono i mock per il test delle unità

Se scrivi i tuoi mock e hai due implementazioni effettive, allora un'interfaccia è appropriata. Probabilmente non avremmo questa discussione in primo luogo se il tuo codebase avesse sia un FooImpl che un TestFoo.

Ma se stai usando un framework di simulazione come Moq, EasyMock o Mockito, puoi prendere in giro le classi e non hai bisogno di interfacce. È analogo all'impostazione di foo.method = mockImplementation in un linguaggio dinamico in cui è possibile assegnare metodi.

Abbiamo bisogno di interfacce per seguire Dependency Inversion Principle (DIP)

DIP dice che costruisci per dipendere da contratti (interfacce) e non da implementazioni. Ma una classe è già un contratto e un'astrazione. Ecco a cosa servono le parole chiave pubblico / privato. All'università, l'esempio canonico era qualcosa di simile a una classe Matrix o Polynomial: i consumatori hanno un'API pubblica per creare matrici, aggiungerle ecc. Ma non è loro permesso preoccuparsi se la matrice è implementata in forma sparsa o densa. Non ci sono stati IMatrix o MatrixImpl richiesti per dimostrare quel punto.

Inoltre, il DIP viene sovra-applicato a ogni livello di chiamata di classe / metodo, non solo ai principali limiti del modulo. Un segno che stai applicando DIP eccessivamente è che l'interfaccia e l'implementazione cambiano in blocco, in modo tale che devi toccare due file per apportare una modifica anziché una. Se il DIP viene applicato in modo appropriato, significa che la tua interfaccia non dovrebbe cambiare spesso. Inoltre, un altro segno è che la tua interfaccia ha solo un consumatore reale (la sua applicazione). Storia diversa se stai costruendo una libreria di classi per il consumo in molte app diverse.

Questo è un corollario del punto di zio Bob Martin sulla beffa: dovresti solo prendere in giro i principali confini architettonici. In una webapp l'accesso HTTP e DB sono i principali limiti. Tutte le chiamate di classe / metodo in mezzo non lo sono. Lo stesso vale per DIP.

Vedi anche:

risposta data 26.07.2015 - 14:02
fonte
8

No (YAGNI) , a meno che tu non stia pianificando di scrivere test per altre classi utilizzando questa interfaccia, e quelli i test potrebbero trarre vantaggio dall'interfaccia.

    
risposta data 07.08.2012 - 09:45
fonte
8

Da MSDN :

Interfaces are better suited to situations in which your applications require many possibly unrelated object types to provide certain functionality.

Interfaces are more flexible than base classes because you can define a single implementation that can implement multiple interfaces.

Interfaces are better in situations in which you do not need to inherit implementation from a base class.

Interfaces are useful in cases where you cannot use class inheritance. For example, structures cannot inherit from classes, but they can implement interfaces.

Generalmente nel caso di una singola classe, non sarà necessario implementare un'interfaccia, ma considerando il futuro del tuo progetto, potrebbe essere utile definire formalmente il comportamento necessario delle classi.

    
risposta data 07.08.2012 - 09:56
fonte
5

Per rispondere alla domanda: c'è dell'altro.

Un aspetto importante di un'interfaccia è l'intento.

Un'interfaccia è "un tipo astratto che non contiene dati, ma espone comportamenti" - Interfaccia (informatica) Quindi, se questo è un comportamento o un insieme di comportamenti supportati dalla classe, è probabile che un'interfaccia sia il modello corretto. Se, tuttavia, il comportamento è (sono) intrinseco al concetto incarnato dalla classe, allora probabilmente non vuoi affatto un'interfaccia.

La prima domanda da porsi è qual è la natura della cosa o del processo che stai cercando di rappresentare. Quindi segui le ragioni pratiche per implementare quella natura in un determinato modo.

    
risposta data 07.08.2012 - 14:55
fonte
5

Dato che hai posto questa domanda, suppongo che tu abbia già visto l'interesse di avere un'interfaccia che nasconde più implementazioni. Questo può essere manifestato dal principio di inversione di dipendenza.

Tuttavia, la necessità di avere un'interfaccia o meno non dipende dal numero delle sue implementazioni. Il vero ruolo di un'interfaccia è che definisce un contratto che indica quale servizio dovrebbe essere fornito al posto di come dovrebbe essere implementato.

Una volta definito il contratto, due o più team possono lavorare in modo indipendente. Diciamo che stai lavorando su un modulo A e dipende dal modulo B, il fatto di creare un'interfaccia su B consente di continuare il tuo lavoro senza preoccuparti dell'implementazione di B perché tutti i dettagli sono nascosti dall'interfaccia. Pertanto, la programmazione distribuita diventa possibile.

Anche se il modulo B ha una sola implementazione della sua interfaccia, l'interfaccia è ancora necessaria.

In conclusione, un'interfaccia nasconde i dettagli di implementazione dai suoi utenti. La programmazione dell'interfaccia aiuta a scrivere più documenti perché è necessario definire un contratto, scrivere software più modulare, promuovere test unitari e accelerare la velocità di sviluppo.

    
risposta data 13.12.2012 - 11:56
fonte
4

Tutte le risposte qui sono molto buone. Infatti la maggior parte delle volte non è necessario per implementare un'interfaccia diversa. Ma ci sono casi in cui potrebbe voler farlo comunque. Ecco alcuni casi in cui lo faccio:

La classe implementa un'altra interfaccia che non voglio esporre
Capita spesso con la classe dell'adattatore che collega il codice di terze parti.

interface NameChangeListener { // Implemented by a lot of people
    void nameChanged(String name); 
} 

interface NameChangeCount { // Only implemented by my class
    int getCount();
}

class NameChangeCounter implements NameChangeListener, NameChangeCount {
    ...
}

class SomeUserInterface {
    private NameChangeCount currentCount; // Will never know that you can change the counter
}

La classe usa una tecnologia specifica che non dovrebbe perdere attraverso Principalmente quando si interagisce con le librerie esterne. Anche se esiste una sola implementazione, utilizzo un'interfaccia per assicurarmi di non introdurre un accoppiamento non necessario con la libreria esterna.

interface SomeRepository { // Guarantee that the external library details won't leak trough
    ...
}

class OracleSomeRepository implements SomeRepository { 
    ... // Oracle prefix allow us to quickly know what is going on in this class
}

Comunicazione cross-layer
Anche se solo una classe UI implementa mai una classe di dominio, consente una migliore separazione tra questi layer e, soprattutto, evita la dipendenza ciclica.

package project.domain;

interface UserRequestSource {
    public UserRequest getLastRequest();
}

class UserBehaviorAnalyser {
    private UserRequestSource requestSource;
}

package project.ui;

class OrderCompleteDialog extends SomeUIClass implements project.domain.UserRequestSource {
    // UI concern, no need for my domain object to know about this method.
    public void displayLabelInErrorMode(); 

    // They most certainly need to know about *that* though
    public UserRequest getLastRequest();
}

Solo un sottoinsieme del metodo dovrebbe essere disponibile per la maggior parte degli oggetti
Principalmente accade quando ho qualche metodo di configurazione sulla classe concreta

interface Sender {
    void sendMessage(Message message)
}

class PacketSender implements Sender {
    void sendMessage(Message message);
    void setPacketSize(int sizeInByte);
}

class Throttler { // This class need to have full access to the object
    private PacketSender sender;

    public useLowNetworkUsageMode() {
        sender.setPacketSize(LOW_PACKET_SIZE);
        sender.sendMessage(new NotifyLowNetworkUsageMessage());

        ... // Other details
    }
}

class MailOrder { // Not this one though
    private Sender sender;
}

Quindi alla fine utilizzo l'interfaccia per la stessa ragione per cui utilizzo il campo privato: l'altro oggetto non dovrebbe avere accesso a cose a cui non dovrebbero accedere. Se ho un caso del genere, introduco un'interfaccia anche se solo una classe one la implementa.

    
risposta data 13.12.2012 - 20:55
fonte
2

Le interfacce sono davvero importanti ma prova a controllare il numero di esse che hai.

Avendo intrapreso la strada della creazione di interfacce per quasi tutto, è facile finire con il codice "spaghetti tagliati". Mi rimetto alla grande saggezza di Ayende Rahien che ha pubblicato alcune parole molto sagge sull'argomento:

link

Questo è il suo primo post di un'intera serie, quindi continua a leggere!

    
risposta data 07.08.2012 - 15:30
fonte
2

Un motivo per cui potresti ancora voler introdurre un'interfaccia in questo caso è seguire il principio di inversione delle dipendenze . Cioè, il modulo che usa la classe dipenderà da un'astrazione di esso (cioè l'interfaccia) invece di dipendere da un'implementazione concreta. Disaccoppia i componenti di alto livello dai componenti di basso livello.

    
risposta data 08.08.2012 - 03:50
fonte
2

Non c'è una vera ragione per fare qualcosa. Le interfacce aiutano e non il programma di output. Quindi, anche se l'interfaccia è implementata da un milione di classi, non esiste una regola che dice che devi crearne una. Ne crei uno in modo tale che quando tu, o chiunque altro usi il tuo codice, desideri cambiare qualcosa che trasmette a tutte le implementazioni. La creazione di un'interfaccia ti aiuterà in tutti i casi futuri in cui potresti voler creare un'altra classe che la implementa.

    
risposta data 13.12.2012 - 18:43
fonte
1

Non è sempre necessario definire un'interfaccia per una classe.

Oggetti semplici come oggetti valore non hanno più implementazioni. Non hanno nemmeno bisogno di essere derisi. L'implementazione può essere verificata da sola e quando vengono testate altre classi che dipendono da esse, è possibile utilizzare l'oggetto valore attuale.

Ricorda che la creazione di un'interfaccia ha un costo. Deve essere aggiornato durante l'implementazione, ha bisogno di un file aggiuntivo e alcuni IDE avranno problemi nello zoom dell'implementazione, non dell'interfaccia.

Quindi definisco le interfacce solo per le classi di livello superiore, dove vuoi un'astrazione dall'implementazione.

Nota che con una classe ottieni un'interfaccia gratis. Oltre all'implementazione, una classe definisce un'interfaccia dall'insieme di metodi pubblici. Quell'interfaccia è implementata da tutte le classi derivate. Non è propriamente un'interfaccia, ma può essere utilizzata esattamente allo stesso modo. Quindi non penso sia necessario ricreare un'interfaccia che esiste già sotto il nome della classe.

    
risposta data 25.08.2014 - 12:12
fonte

Leggi altre domande sui tag