Ha senso definire un'interfaccia se ho già una classe astratta?


12

Ho un corso con alcune funzionalità predefinite / condivise. Io lo uso abstract classper questo:

public interface ITypeNameMapper
{
    string Map(TypeDefinition typeDefinition);
}

public abstract class TypeNameMapper : ITypeNameMapper
{
    public virtual string Map(TypeDefinition typeDefinition)
    {
        if (typeDefinition is ClassDefinition classDefinition)
        {
            return Map(classDefinition);
        }
        ...

        throw new ArgumentOutOfRangeException(nameof(typeDefinition));
    }

    protected abstract string Map(ClassDefinition classDefinition);
}

Come puoi vedere, ho anche l'interfaccia ITypeNameMapper. Ha senso definire questa interfaccia se ho già una classe astratta TypeNameMappero abstract classè abbastanza?

TypeDefinition anche in questo esempio minimale è astratto.


4
Cordiali saluti - in OOD, il controllo dei tipi nel codice è un'indicazione che la gerarchia delle classi non è corretta - ti sta dicendo di creare classi derivate dal tipo che stai controllando. Quindi, in questo caso, si desidera un'interfaccia ITypeMapper che specifica un metodo Map () e quindi implementare tale interfaccia nelle classi TypeDefinition e ClassDefinition. In questo modo, ClassAssembler scorre semplicemente l'elenco di ITypeMappers, chiamando Map () su ciascuno e ottiene la stringa desiderata da entrambi i tipi perché ognuno ha la propria implementazione.
Rodney P. Barbati,

1
L'uso di una classe base al posto di un'interfaccia, a meno che non sia assolutamente necessario, si chiama "masterizzazione della base". Se può essere fatto con un'interfaccia, fallo con un'interfaccia. La classe astratta diventa quindi un utile extra. artima.com/intv/dotnet.html In particolare, il remoting richiede di derivare da MarshalByRefObject, quindi se si desidera essere remotati non è possibile derivare da nient'altro.
Ben,

@ RodneyP.Barbati non funzionerà se voglio avere molti mappatori per lo stesso tipo che posso cambiare a seconda della situazione
Konrad,

1
@Ben sta bruciando la base che è interessante. Grazie per la gemma!
Konrad,

@Konrad Questo non è corretto: non c'è nulla che ti impedisca di implementare l'interfaccia chiamando un'altra implementazione dell'interfaccia, quindi ogni tipo può avere un'implementazione collegabile che esponi tramite l'implementazione nel tipo.
Rodney P. Barbati,

Risposte:


31

Sì, perché C # non consente l'ereditarietà multipla se non con le interfacce.

Quindi, se ho una classe che è sia TypeNameMapper che SomethingelseMapper, posso fare:

class MultiFunctionalClass : ITypeNameMapper, ISomethingelseMapper 
{
    private TypeNameMapper map1
    private SomethingelseMapper map2

    public string Map(TypeDefinition typeDefinition) { return map1.Map(typeDefintion);}

    public string Map(OtherDef otherDef) { return map2.Map(orderDef); }
}

4
@Liath: la singola responsabilità è in realtà un fattore che contribuisce al motivo per cui è necessaria l'eredità. Consideriamo tre possibili tratti: ICanFly, ICanRun, ICanSwim. Devi creare 8 classi di creature, una per ogni combinazione di tratti (YYY, YYN, YNY, ..., NNY, NNN). L'SRP dice che si implementa ogni comportamento (volare, correre, nuotare) una volta e poi implementarli in modo riutilizzabile sulle 8 creature. La composizione sarebbe ancora migliore qui, ma ignorando la composizione per un secondo, dovresti già vedere la necessità di ereditare / implementare più comportamenti riutilizzabili separati a causa dell'SRP.
Flater,

4
@Konrad è quello che intendo. Se scrivi l'interfaccia e non ne hai mai bisogno, il costo è di 3 LoC. Se non scrivi l'interfaccia e scopri di averne bisogno, il costo potrebbe essere il refactoring dell'intera applicazione.
Ewan,

3
(1) L'implementazione delle interfacce è eredità? (2) Sia la domanda che la risposta devono essere qualificate. "Dipende." (3) L'ereditarietà multipla necessita di un adesivo di avvertimento. (4) Potrei mostrarti K di interfacenegligente formalità malvagia .. implementazioni ridondanti che dovrebbero essere nella base - che si stanno evolvendo in modo indipendente! Il mio preferito: classi con 1 metodo ciascuna implementando il proprio metodo 1 interface, ciascuna con una sola istanza. ... ecc, ecc., ecc.
radarbob,

3
C'è un coefficiente di attrito invisibile nel codice. Lo senti quando sei costretto a leggere interfacce superflue. Quindi si mostra riscaldando come lo space shuttle che sbatte nell'atmosfera mentre un'implementazione dell'interfaccia rende la classe astratta astratta. È la Twilight Zone e questo è lo shuttle Challenger. Accettando il mio destino, guardo il plasma infuriato con stupore senza paura e distaccato. L'attrito insopportabile squarcia la facciata della perfezione, depositando la massa frantumata in produzione con un fragoroso fragore.
radarbob,

4
@radarbob Poetic. ^ _ ^ Concordo; mentre il costo delle interfacce è basso, non è inesistente. Non tanto nello scriverli, ma in una situazione di debug sono 1-2 altri passaggi per arrivare al comportamento reale, che possono sommarsi rapidamente quando si ottiene una mezza dozzina di livelli di astrazione in profondità. In una biblioteca, di solito ne vale la pena. Ma in un'applicazione, il refactoring è meglio della generalizzazione prematura.
Errori

1

Le interfacce e le classi astratte hanno scopi diversi:

  • Le interfacce definiscono le API e appartengono ai client e non alle implementazioni.
  • Se le classi condividono implementazioni, allora potresti beneficiare di una classe astratta .

Nel tuo esempio, interface ITypeNameMapperdefinisce le esigenze dei clienti e abstract class TypeNameMappernon aggiunge alcun valore.


2
Anche le classi astratte appartengono ai clienti. Non so cosa intendi con questo. Tutto ciò che publicappartiene al cliente. TypeNameMappersta aggiungendo molto valore perché salva l'implementatore dalla scrittura di una logica comune.
Konrad,

2
Il fatto che un'interfaccia sia presentata interfaceo meno è rilevante solo in quanto C # proibisce la piena eredità multipla.
Deduplicatore,

0

L'intero concetto di interfacce è stato creato per supportare una famiglia di classi con un'API condivisa.

Questo dice esplicitamente che qualsiasi uso di un'interfaccia implica che esiste (o si prevede che ci sarà) più di una implementazione delle sue specifiche.

I framework DI hanno confuso le acque qui in quanto molti di loro richiedono un'interfaccia anche se ci sarà sempre una sola implementazione - per me questo è irragionevole sovraccarico per quello che nella maggior parte dei casi è solo un mezzo più complesso e più lento di chiamare nuovo, ma è quello che è.

La risposta sta nella domanda stessa. Se hai una classe astratta, ti stai preparando per la creazione di più di una classe derivata con un'API comune. Pertanto, l'uso di un'interfaccia è chiaramente indicato.


2
"Se hai una classe astratta, ... l'uso di un'interfaccia è chiaramente indicato." - La mia conclusione è l'opposto: se hai una classe astratta, allora usala come se fosse un'interfaccia (se il DI non gioca con essa, considera un'implementazione diversa). Solo quando non funziona davvero, crea l'interfaccia: puoi farlo in qualsiasi momento dopo.
maaartinus,

1
Idem, @maaartinus. E nemmeno "... come se fosse un'interfaccia". I membri pubblici di una classe sono un'interfaccia. Idem anche per "puoi farlo in qualsiasi momento dopo"; questo va al cuore dei fondamenti del design 101.
radarbob,

@maaartinus: se si crea un'interfaccia dall'inizio, ma si utilizzano riferimenti di tipo di classe astratta ovunque, l'esistenza dell'interfaccia non aiuterà nulla. Se, tuttavia, il codice client utilizza il tipo di interfaccia anziché il tipo di classe astratta ogni volta che è possibile, ciò faciliterà notevolmente le cose se si rendesse necessario produrre un'implementazione internamente molto diversa dalla classe astratta. La modifica del codice client a quel punto per utilizzare l'interfaccia sarà molto più dolorosa della progettazione del codice client per farlo dall'inizio.
supercat,

1
@supercat Potrei essere d'accordo supponendo che tu stia scrivendo una biblioteca. Se si tratta di un'applicazione, non vedo alcun problema in sostituzione di tutte le occorrenze contemporaneamente. My Eclipse può farlo (Java, non C #), ma se non è possibile, è proprio come find ... -exec perl -pi -e ...ed eventualmente correggendo errori di compilazione banali. Il mio punto è: il vantaggio di non avere una cosa (attualmente) inutile è molto più grande dei possibili risparmi futuri.
maaartinus,

La prima frase sarebbe corretta, se ti contrassegnavi interfacecome codice (stai parlando di C # - interfaces, non di un'interfaccia astratta, come qualsiasi insieme di classi / funzioni / ecc.), E l'avrebbe terminata "... ma ancora fuorilegge il multiplo completo eredità." Non c'è bisogno di interfaces se hai l'MI.
Deduplicatore,

0

Se vogliamo rispondere esplicitamente alla domanda, l'autore dice "Ha senso definire questa interfaccia se ho già una classe astratta TypeNameMapper o una classe astratta è appena sufficiente?"

La risposta è sì e no - Sì, dovresti creare l'interfaccia anche se hai già la classe base astratta (perché non dovresti fare riferimento alla classe base astratta in nessun codice client) e no, perché non avresti dovuto creare la classe base astratta in assenza di un'interfaccia.

È evidente che non hai pensato abbastanza all'API che stai cercando di creare. Procurati prima l'API, quindi, se lo desideri, fornisci un'implementazione parziale. Il fatto che la tua classe base astratta digiti il ​​check-in nel codice è sufficiente per dirti che non è l'astrazione giusta.

Come nella maggior parte delle cose in OOD, quando sei nel groove e hai fatto bene il tuo modello a oggetti, il tuo codice ti informerà su ciò che verrà dopo. Inizia con un'interfaccia, implementa quell'interfaccia nelle classi che ti servono. Se ti ritrovi a scrivere un codice simile, estrailo in una classe base astratta: è l'interfaccia che è importante, la classe base astratta è solo un aiuto e potrebbe essercene più di uno.


-1

È abbastanza difficile rispondere senza conoscere il resto dell'applicazione.

Supponendo che tu stia utilizzando un qualche tipo di modello DI e che il tuo codice sia stato progettato per essere il più estendibile possibile (in modo da poterne aggiungere TypeNameMapperfacilmente altri), direi di sì.

Ragioni per:

  • Lo ottieni effettivamente gratuitamente, poiché la classe base implementa il interfacenon devi preoccupartene in nessuna implementazione figlio
  • La maggior parte dei framework IoC e delle librerie beffardo si aspettano interfaces. Mentre molti di loro lavorano con classi astratte non è sempre scontato e credo fermamente nel seguire lo stesso percorso dell'altro 99% degli utenti di una biblioteca, ove possibile.

Ragioni contro:

  • A rigor di termini (come hai sottolineato) non è davvero necessario
  • Se il class/ interfacecambia c'è un piccolo overhead aggiuntivo

Tutto considerato, direi che dovresti creare il interface. Le ragioni contro sono abbastanza trascurabili ma, sebbene sia possibile utilizzare gli abstract in derisione e IoC, è spesso molto più semplice con le interfacce. Alla fine, però, non criticherei il codice di un collega se andasse dall'altra parte.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.