Perché la maggior parte gli architetti di sistema insistere sulla prima programmazione di un'interfaccia?

StackOverflow https://stackoverflow.com/questions/48605

Domanda

Quasi ogni Java libro che ho letto parla usando l'interfaccia come un modo per condividere lo stato e il comportamento tra gli oggetti prima volta "costruito" non sembrano condividere una relazione.

Tuttavia, ogni volta che vedo architetti design di un'applicazione, la prima cosa che fanno è di iniziare la programmazione di un'interfaccia.Come mai?Come si fa a conoscere tutte le relazioni tra gli oggetti che si verifica all'interno di tale interfaccia?Se si conosce già queste relazioni, allora perché non estendere una classe astratta?

È stato utile?

Soluzione

Programmazione di un'interfaccia significa rispettare il "contratto" creata usando l'interfaccia.E così, se il vostro IPoweredByMotor l'interfaccia ha un start() metodo, future classi che implementano l'interfaccia, siano essi MotorizedWheelChair, Automobile, o SmoothieMaker, nell'attuare i metodi dell'interfaccia, aggiungere flessibilità al sistema, perché un pezzo di codice per avviare il motore di molti tipi diversi di cose, perché tutto ciò che un pezzo di codice ha bisogno di sapere è che esse rispondono a start().Non importa come si avvia, basta che si deve iniziare.

Altri suggerimenti

Grande domanda.Farò riferimento a Josh Bloch Efficace Java, chi scrive (articolo 16) perché preferire l'uso di interfacce con le classi astratte.A proposito, se non hai questo libro, mi raccomando!Ecco un riassunto di quello che dice:

  1. Classi esistenti possono essere facilmente adattati per attuare una nuova interfaccia. Tutto quello che dovete fare è implementare l'interfaccia e aggiungere i metodi necessari.Classi esistenti non possono essere adattati facilmente estendere una nuova classe astratta.
  2. Le interfacce sono ideali per la definizione di mix-ins. Un mix-in e interfaccia consente alle classi di dichiarare aggiuntivi facoltativi comportamento (per esempio, Paragonabile).Permette la funzionalità opzionale di essere mescolato con le funzioni primarie.Le classi astratte non è possibile definire il mix-ins -- classe può estendere più di un genitore.
  3. Interfacce consentono di non gerarchico di quadri. Se si dispone di una classe che ha la funzionalità di molte interfacce, è possibile implementare tutti.Senza interfacce, è necessario creare un gonfio di gerarchia di classi con una classe per ogni combinazione di attributi, con conseguente esplosione combinatoria.
  4. Interfacce per consentire l'esplorazione sicura funzionalità. È possibile creare classi wrapper utilizzando il pattern Decorator, robusto e flessibile.Una classe wrapper implementa e contiene la stessa interfaccia, l'inoltro di alcune funzionalità per metodi esistenti, mentre l'aggiunta specializzata comportamento di altri metodi.Non si può fare questo con i metodi astratti - è necessario utilizzare l'ereditarietà, invece, che è più fragile.

Che cosa circa il vantaggio di classi astratte, fornendo implementazione di base?È possibile fornire un abstract scheletrico classe di implementazione con ogni interfaccia.Questo unisce le virtù di entrambe le interfacce e classi astratte.Scheletrico implementazioni di fornire assistenza nell'implementazione senza imporre i pesanti vincoli che le classi astratte forza quando essi servono come tipo di definizioni.Per esempio, il Collezioni Quadro definisce il tipo di utilizzo delle interfacce, e fornisce uno scheletro di attuazione per ciascuna di esse.

Programmazione di interfacce offre diversi benefici:

  1. Richiesto per GoF tipo di modelli, come il visitor pattern

  2. Consente implementazioni alternative.Per esempio, più di accesso ai dati oggetto di implementazioni possono esistere per una singola interfaccia che astrae il motore di database in uso (AccountDaoMySQL e AccountDaoOracle possono implementare AccountDao)

  3. Una Classe può implementare più interfacce.Java non consente più di ereditarietà delle classi concrete.

  4. Abstract dettagli di implementazione.Le interfacce possono includere solo API pubblica metodi, nascondendo i dettagli di implementazione.I vantaggi includono una correttamente documentato API pubblica e ben documentato contratti.

  5. Ampiamente utilizzato dai moderni dependency injection quadri, come http://www.springframework.org/.

  6. In Java, le interfacce possono essere utilizzate per creare dynamic proxy - http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/Proxy.html.Questo può essere utilizzato in modo molto efficace con l'utilizzo di framework come Molla per effettuare la Programmazione Orientata agli aspetti.Aspetti può aggiungere molto utile la funzionalità di Classi, senza aggiungere direttamente il codice java per quelle classi.Esempi di questa funzionalità includono la registrazione, il controllo, il monitoraggio delle prestazioni, la transazione di demarcazione, etc. http://static.springframework.org/spring/docs/2.5.x/reference/aop.html.

  7. Mock implementazioni, unit testing, - Quando il dipendente classi sono implementazioni di interfacce, mock classi può essere scritto che anche l'attuazione di tali interfacce.Il mock classi possono essere utilizzate per facilitare i test di unità.

Penso che una delle ragioni per le classi astratte sono state in gran parte abbandonato dagli sviluppatori potrebbe essere un malinteso.

Quando il I Gang of Four ha scritto:

Programma di interfaccia non un'implementazione.

non c'era alcuna cosa come java o C# interfaccia.Stavano parlando del object-oriented " concetto di interfaccia, che ogni classe ha.Erich Gamma cita in questa intervista.

Penso che seguendo tutte le regole e i principi meccanicamente, senza pensare comporta un difficile da leggere, navigare, comprendere e mantenere la base di codice.Ricordate:La cosa più semplice che potrebbe funzionare.

Come mai?

Perché è quello che tutti i libri che dire.Come GoF modelli, molte persone lo vedono come universalmente bene e non pensare mai a se o non è proprio il design.

Come si fa a conoscere tutte le relazioni tra gli oggetti che si verifica all'interno di tale interfaccia?

Non è, e questo è un problema.

Se sai già queste relazioni, allora perché non estendere un abstract classe?

Motivi per non estendere una classe astratta:

  1. Si sono radicalmente diverse implementazioni e fare una buona classe di base è troppo difficile.
  2. Avete bisogno di bruciare la vostra unica e sola classe base per qualcos'altro.

Se non si applicano, andare avanti e utilizzare una classe astratta.Essa vi consente di risparmiare un sacco di tempo.

Domande non hai chiesto:

Quali sono i lati di utilizzo di un'interfaccia?

Non è possibile modificarle.A differenza di una classe astratta, un'interfaccia è scolpito nella pietra.Una volta in uso, estendendo si romperà il codice, il periodo.

Ho davvero bisogno di uno?

La maggior parte del tempo, non.Pensi davvero difficile, prima si crea una gerarchia di oggetti.Un grosso problema in linguaggi come Java, che rende troppo facile per creare enormi, complicate gerarchie di oggetti.

Considerato il classico esempio di LameDuck eredita da Anatra.Sembra facile, non è vero?

Bene, fino a quando non è necessario indicare che l'anatra è stato ferito e ora è zoppo.O indicare che il fantoccio è stato guarito e può camminare di nuovo.Java non consente di modificare un oggetto di tipo, in modo da utilizzare sub-tipi di indicare zoppia che in realtà non funziona.

Programmazione di un'interfaccia significa rispettare il "contratto" creata da utilizzando l'interfaccia

Questo è il singolo più frainteso cosa interfacce.

Non c'è modo di far valere tale contratto con interfacce.Interfacce, per definizione, non è possibile specificare qualsiasi comportamento a tutti.Classi in cui il comportamento accade.

Questa errata convinzione è così diffusa da essere considerata la saggezza convenzionale da molte persone.È, tuttavia, sbagliato.

Quindi questa dichiarazione in OP

Quasi ogni Java libro che ho letto parla usando l'interfaccia come un modo per condividere lo stato e il comportamento tra gli oggetti

non è solo possibile.Le interfacce non hanno né stato, né il comportamento.Si possono definire le proprietà, che l'implementazione delle classi deve fornire, ma che è il più vicino come si può ottenere.Non è possibile condividere il comportamento di uso di interfacce.

È possibile effettuare un presupposto che la gente di implementare un'interfaccia per fornire il tipo di comportamento implicito nel nome dei suoi metodi, ma non è niente come la stessa cosa.E non pone restrizioni su quando tali metodi sono chiamati (ad esempio Inizio dovrebbe essere chiamato prima Fermata).

Questa dichiarazione

Richiesto per GoF tipo di modelli, come il visitor pattern

inoltre non è corretto.GoF libro utilizza esattamente zero interfacce, in quanto non erano una caratteristica dei linguaggi utilizzati al momento.Nessuno dei modelli richiedono interfacce, anche se alcuni possono usare.IMO, il pattern Observer è quello in cui le interfacce possono svolgere un più elegante di ruolo (anche se il modello è normalmente implementato utilizzando gli eventi di oggi).Nel Visitor pattern è quasi sempre il caso che una base Visitatore classe che implementa il comportamento di default per ogni tipo di nodo visitato è richiesto, IME.

Personalmente, penso che la risposta alla domanda è triplice:

  1. Le interfacce sono visti da molti come un proiettile d'argento (di solito queste persone lavoro sotto il "contratto" un malinteso, o pensi che le interfacce magicamente a scindere il loro codice)

  2. Java persone sono molto concentrati sull'utilizzo di quadri, molti dei quali (giustamente) richiedono classi per implementare le loro interfacce

  3. Le interfacce sono state il modo migliore per fare alcune cose prima di farmaci generici e le annotazioni (attributi in C#) sono stati introdotti.

Le interfacce sono molto utili funzionalità del linguaggio, ma sono molto abusato.I sintomi includono:

  1. Un'interfaccia è implementata solo da una classe

  2. Una classe implementa le interfacce multiple.Spesso propagandato come un vantaggio di interfacce, di solito significa che la classe in questione è violazione del principio di separazione delle competenze.

  3. C'è una gerarchia di ereditarietà di interfaccia (spesso si riflette in una gerarchia di classi).Questa è la situazione che si sta cercando di evitare utilizzando interfacce, in primo luogo.Troppo eredità è una brutta cosa, sia per le classi e le interfacce.

Tutte queste cose sono il codice di odori, IMO.

E ' un modo per promuovere sciolto accoppiamento.

Con un basso accoppiamento, un cambiamento in un modulo non richiede un cambiamento nell'attuazione di un altro modulo.

Un buon uso di questo concetto è Astratto modello Factory.In Wikipedia esempio, GUIFactory interfaccia produce Pulsante di interfaccia.La fabbrica di cemento può essere WinFactory (produzione di WinButton), o OSXFactory (produzione di OSXButton).Immaginate se si sta scrivendo una applicazione GUI e devi andare a cercare in giro tutte le istanze di OldButton la classe e la modifica di WinButton.Poi per il prossimo anno, è necessario aggiungere OSXButton versione.

A mio parere, si vede tanto spesso, perché è una buona pratica che viene spesso applicato in situazioni errate.

Ci sono molti vantaggi per le interfacce relative a classi astratte:

  • È possibile passare implementazioni w/o ri-creazione di codice che dipende dall'interfaccia.Questo è utile per:classi proxy, dependency injection, AOP, etc.
  • È possibile separare le API dall'implementazione nel codice.Questo può essere molto piacevole, in quanto rende evidente quando si cambia il codice che interessa altri moduli.
  • Esso consente agli sviluppatori di scrivere codice che dipende dal vostro codice facilmente in giro per le API per scopi di test.

Si ottenere il massimo vantaggio dalle interfacce quando trattare con i moduli di codice.Tuttavia, non c'è una semplice regola per determinare dove il modulo confini deve essere.Così la pratica è molto facile utilizzo, soprattutto in fase di progettazione di un software.

Vorrei assumere (con @eed3s9n) che è di promuovere l'accoppiamento.Inoltre, senza interfacce di unit test, diventa molto più difficile, in quanto non si può deridere i tuoi oggetti.

Perché si estende è male.Questo articolo è praticamente una risposta diretta alla domanda posta.Posso pensare quasi nessun caso in cui si sarebbe in realtà bisogno una classe astratta, e un sacco di situazioni in cui non è una cattiva idea.Questo non significa che le implementazioni utilizzando le classi astratte sono male, ma si dovrà prendere cura, in modo da non rendere l'interfaccia contratto dipendente su manufatti di alcune specifiche di attuazione (caso in questione:lo Stack di classe in Java).

Ancora una cosa:non è necessario, o buone pratiche, di interfacce ovunque.In genere, è necessario identificare se avete bisogno di un interfaccia e quando non.In un mondo ideale, il secondo caso dovrebbe essere implementato come un finale di classe la maggior parte del tempo.

Ci sono delle ottime risposte qui, ma se siete in cerca di una ragione concreta, non cercate di Test di Unità.

Considera che si desidera testare un metodo di logica di business che recupera l'attuale tasso di imposta per la regione in cui una transazione si verifica.Per fare questo, la logica di business di classe a parlare al database tramite un Repository:

interface IRepository<T> { T Get(string key); }

class TaxRateRepository : IRepository<TaxRate> {
    protected internal TaxRateRepository() {}
    public TaxRate Get(string key) {
    // retrieve an TaxRate (obj) from database
    return obj; }
}

In tutto il codice, utilizzare il tipo di IRepository invece di TaxRateRepository.

Il repository non ha un costruttore pubblico per incoraggiare gli utenti (gli sviluppatori) per utilizzare la fabbrica di creare un'istanza del repository:

public static class RepositoryFactory {

    public RepositoryFactory() {
        TaxRateRepository = new TaxRateRepository(); }

    public static IRepository TaxRateRepository { get; protected set; }
    public static void SetTaxRateRepository(IRepository rep) {
        TaxRateRepository = rep; }
}

La fabbrica è l'unico luogo in cui il TaxRateRepository classe fa direttamente.

Quindi, avete bisogno di alcune classi di supporto per questo esempio:

class TaxRate {
    public string Region { get; protected set; }
    decimal Rate { get; protected set; }
}

static class Business {
    static decimal GetRate(string region) { 
        var taxRate = RepositoryFactory.TaxRateRepository.Get(region);
        return taxRate.Rate; }
}

E c'è anche un'altra implementazione di IRepository - il mock up:

class MockTaxRateRepository : IRepository<TaxRate> {
    public TaxRate ReturnValue { get; set; }
    public bool GetWasCalled { get; protected set; }
    public string KeyParamValue { get; protected set; }
    public TaxRate Get(string key) {
        GetWasCalled = true;
        KeyParamValue = key;
        return ReturnValue; }
}

Perché il codice dal vivo (Business Class), che utilizza una Fabbrica per ottenere il Repository, nel test di unità per collegare il MockRepository per il TaxRateRepository.Una volta che la sostituzione è fatta, è possibile codificare il valore di ritorno e rendere il database superflua.

class MyUnitTestFixture { 
    var rep = new MockTaxRateRepository();

    [FixtureSetup]
    void ConfigureFixture() {
        RepositoryFactory.SetTaxRateRepository(rep); }

    [Test]
    void Test() {
        var region = "NY.NY.Manhattan";
        var rate = 8.5m;
        rep.ReturnValue = new TaxRate { Rate = rate };

        var r = Business.GetRate(region);
        Assert.IsNotNull(r);
        Assert.IsTrue(rep.GetWasCalled);
        Assert.AreEqual(region, rep.KeyParamValue);
        Assert.AreEqual(r.Rate, rate); }
}

Ricordate, si desidera testare la logica di business solo metodo, non il repository, database, la stringa di connessione, ecc...Ci sono diversi test per ognuno di essi.Facendo in questo modo, è possibile isolare completamente il codice che si sta testando.

Un vantaggio collaterale è che si può anche eseguire il test di unità senza una connessione a un database, che rende più veloce, più portatile (si pensi multi-team di sviluppatori in località remote).

Un altro vantaggio collaterale è che si può utilizzare il Test-Driven Development (TDD) processo per l'attuazione della fase di sviluppo.Io non strettamente utilizzare TDD, ma un mix di TDD e di vecchia scuola di codifica.

In un certo senso, penso che la tua domanda si riduce semplicemente, "perché l'uso di interfacce e classi astratte non?" Tecnicamente, si può ottenere accoppiamento con entrambi-l'implementazione non è ancora esposta al codice chiamante, ed è possibile utilizzare Abstract Factory pattern per la restituzione di un'implementazione sottostante (implementazione dell'interfaccia vsclasse astratta estensione) per aumentare la flessibilità di progettazione.In effetti, si potrebbe sostenere che le classi astratte dare leggermente di più, in quanto consentono di entrambi richiedono implementazioni per soddisfare il vostro codice ("si DEVE implementare il metodo start()") e forniscono implementazioni di default ("ho una vernice standard() non si può ignorare se si vuole") -- con le interfacce, le implementazioni devono essere forniti, che nel tempo può portare a fragili eredità di problemi attraverso modifiche dell'interfaccia.

Fondamentalmente, però, io uso le interfacce principalmente a causa di Java ereditarietà singola restrizione.Se la mia applicazione DEVE ereditare da una classe astratta per essere utilizzato dal codice chiamante, il che significa che mi perde la flessibilità di ereditare da qualcos'altro, anche se questo può rendere più senso (ad es.per il riutilizzo del codice o di un oggetto gerarchia).

Una ragione è che le interfacce di consentire la crescita e l'estensibilità.Dire, per esempio, che si dispone di un metodo che accetta un oggetto come parametro,

public void bere(caffè someDrink) {

}

Ora diciamo che si desidera utilizzare esattamente lo stesso metodo, ma passare un hotTea oggetto.Beh, non si può.Appena hard-coded che il metodo di cui sopra per l'utilizzo solo di caffè oggetti.Forse questo è un bene, forse è un male.Il rovescio della medaglia di quanto sopra è che è strettamente blocca con un tipo di oggetto, quando si desidera passare tutti i tipi di oggetti correlati.

Utilizzando un'interfaccia, dire IHotDrink,

interfaccia IHotDrink { }

e rewrting il metodo di cui sopra per utilizzare l'interfaccia invece dell'oggetto,

public void bere(IHotDrink someDrink) {

}

Ora è possibile passare tutti gli oggetti che implementano l'IHotDrink interfaccia.Certo, si può scrivere esattamente la stessa metodo che fa esattamente la stessa cosa con un oggetto diverso parametro, ma perché?Sei improvvisamente mantenere gonfio codice.

Tutti i suoi circa la progettazione prima di codifica.

Se non conoscete tutte le relazioni tra i due oggetti dopo aver specificato l'interfaccia poi hanno fatto un pessimo lavoro di definizione dell'interfaccia, che è relativamente facile da risolvere.

Se si era buttata direttamente nel codice e realizzato a metà strada attraverso di voi, sono mancanti di qualcosa di molto più difficile da risolvere.

Questo potrebbe essere visto da un perl/python/ruby prospettiva :

  • quando si passa un oggetto come parametro di un metodo che non passa è il tipo , sai che deve rispondere ad alcuni metodi

Penso che considerando le interfacce java come un'analogia che sarebbe meglio spiegare questo .In realtà non passa un tipo , basta passare a qualcosa che risponde a un metodo ( un tratto , se volete ).

Penso che il motivo principale per utilizzare le interfacce in Java è il limite per l'ereditarietà singola.In molti casi, comportare un'inutile complicazione e la duplicazione del codice.Date un'occhiata a Tratti in Scala: http://www.scala-lang.org/node/126 I tratti sono un tipo speciale di classi astratte, ma una classe può estendere a molti di loro.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top