Ha senso utilizzare le interfacce se non si dispone di polimorfismo?

3

Supponiamo che abbia classi distinte che hanno lo stesso comportamento, che può essere rappresentato in questo modo:

public interface Behavior {
    void operationA();
}

public class ImplementerA implements Behavior {
    public void operationA() {

    }
}

public class ImplementerB implements Behavior {
    public void operationA() {

    }
}

Ma ImplementerA e ImplementerB sono usati da client distinti; ClientA utilizza solo ImplementerA e ClientB usa solo ImplementerB ; oltre a questi, non ci sono altri client per ImplementerA e ImplementerB .

In questo scenario, l'interfaccia Behavior ha uno scopo? Dovrebbe essere creato perché forse i requisiti futuri cambiano ...

    
posta m3th0dman 15.07.2014 - 14:55
fonte

2 risposte

2

Se stai utilizzando un linguaggio in cui le classi devono essere codificate nel codice (ad es. Java / C #), allora potrebbe essere logico utilizzare un'interfaccia nel caso tu voglia modificare l'implementazione di Behavior che viene passato a ClientA o ClientB senza ricompilare. Questo di solito accade quando si desidera testare il codice unitario fornendo implementazioni fittizie di Behavior . Senza un'interfaccia, dovresti modificare la dichiarazione di importazione del codice client in modo che faccia riferimento alla corretta implementazione e ricompilila.

Nelle lingue che non fanno riferimento al codice sorgente in classi che utilizzano identificatori univoci globali (che sono i nomi completi come com.yoursite.Foo) non è un problema e non è necessario creare un interfaccia. Tuttavia, le lingue con questo tipo di sistema di moduli sono linguaggi funzionali relativamente impopolari (ad esempio Standard ML, OCaml) quindi probabilmente non si applicano a te.

Si noti, tuttavia, che non tutte le classi possono essere implementate come un'interfaccia. Prendere in considerazione:

public class LooksAtOthers {
    public LooksAtOthers binaryOperation(LooksAtOthers other) {
        ...
    }
}

L'operazione binaria qui esamina i campi privati di other . Questo risulta molto quando si implementano strutture dati; per fare fusioni o unioni efficienti devi essere in grado di guardare all'implementazione di entrambi gli operandi. Questo non può essere fatto da un'interfaccia:

public interface CantLook {
    CantLook binaryOperation(CantLook other)
}

public class CantLookImpl implements CantLook {
    public CantLook binaryOperation(CantLook other) {
        // no idea what type CantLook has
    }
}

Qui CantLook è un'interfaccia, quindi la classe che implementa CantLook non ha idea di cosa siano i campi privati other . Di conseguenza se binaryOperation ha richiesto di guardare all'interno di other , non può più essere implementato in modo efficiente.

Si noti inoltre che l'uso di una classe rende il debug un po 'più semplice. Con una classe ogni dato codice cliente interagisce solo con una implementazione; funziona o no. Con un'interfaccia ci può essere un numero qualsiasi di implementazioni e nulla impedisce a qualcuno di crearne uno cattivo. Se utilizzi un'interfaccia e ClientA interruzioni, devi considerare quale implementazione di Behavior l'ha interrotta. Qualcuno potrebbe averlo creato e passato in un buggy ImplementerC .

Quindi la linea di fondo è: dipende. Ti dà fastidio dover cambiare le istruzioni di importazione? Stai facendo test unitari? Hai bisogno di guardare ad altre istanze della classe?

    
risposta data 15.07.2014 - 15:13
fonte
2

Il motivo principale per utilizzare le interfacce è spiegato nel principio dell'inversione delle dipendenze (la D in SOLID).

  • I moduli di alto livello non dovrebbero dipendere da moduli di basso livello. Entrambi dovrebbero dipendere dalle astrazioni.
  • Le astrazioni non dovrebbero dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni (***).

L'obiettivo è disaccoppiare il livello più alto dalle implementazioni specifiche, consentendo una maggiore manutenibilità.

Il problema principale della manutenibilità è compensare l'incapacità degli esseri umani di prevedere il futuro.

Istruzioni come "ClientA utilizza solo ImplementerA e ClientB usano solo ImplementerB" ha dimostrato di non essere valido per sempre.

*** NOTA: nel contesto di SOLID e OOD, "astrazione" significa che non può essere istanziato, come una classe astratta o un'interfaccia.

Qualcosa che non può essere istanziato deve avere pochissime ragioni per cambiare gli straordinari. In questo modo il codice che dipende dalle astrazioni dipende da cose che sono stabili (il cosa e il come). Il codice che dipende dalle astrazioni ha meno motivi per cambiare quel codice che dipende dalle concrezioni.

    
risposta data 15.07.2014 - 15:46
fonte