Riduzione del boilerplate in classe che implementa le interfacce attraverso la composizione


11

Ho una classe: Aquesto è un composto di un numero di classi più piccole B, Ce D.

B, CE Dimplementare interfacce IB, ICe IDrispettivamente.

Dal momento che Asupporta tutte le funzionalità di B, Ce D, Aattrezzi IB, ICe IDcosì, ma questo purtroppo porta ad un sacco di ri-instradamento per l'attuazione delA

Così:

interface IB
{
    int Foo {get;}
}

public class B : IB
{
    public int Foo {get {return 5;}}
}

interface IC
{
    void Bar();
}

public class C : IC
{
    public void Bar()
    {
    }
}

interface ID
{
    string Bash{get; set;}
}

public class D: ID
{
    public string Bash {get;set;}
}

class A : IB, IC, ID
{
    private B b = new B();
    private C c = new C();
    private D d = new D();

    public int Foo {get {return b.Foo}}

    public A(string bash)
    {
        Bash = bash;
    }

    public void Bar()
    {
        c.Bar();
    }

    public string Bash
    {
         get {return d.Bash;}
         set {d.Bash = value;}
    }
}

C'è un modo per sbarazzarsi di uno qualsiasi di questo reindirizzamento del boilerplate A? B, CE Dtutto implementare funzionalità diverse ma comuni, e simili che Aimplementa IB, ICe IDperché significa posso passare in Asé come dipendenza corrispondenza tali interfacce, e non devono esporre i metodi di supporto interno.


Potresti dire qualcosa sul perché hai bisogno di una classe A per implementare IB, IC, ID? Perché non esporre BCD come pubblico?
Esben Skov Pedersen,

1
Penso che il mio motivo principale per preferire l' Aattrezzo IB, ICed IDè più sensato scrivere Person.Walk()al contrario Person.WalkHelper.Walk(), ad esempio.
Nick Udell,

Risposte:


7

Quello che stai cercando è comunemente chiamato mixins . Purtroppo, C # non supporta nativamente quelli.

Esistono alcune soluzioni alternative: una , due , tre e molte altre.

In realtà mi piace davvero l'ultimo. L'idea di utilizzare la classe parziale generata automaticamente per generare il boilerplate è probabilmente la più vicina possibile a una soluzione davvero valida:

[pMixins] è un plug-in di Visual Studio che esegue la scansione di una soluzione per classi parziali decorate con attributi pMixin. Contrassegnando la tua classe come parziale, [pMixins] può creare un file code-behind e aggiungere altri membri alla tua classe


2

Anche se non riduce la piastra di caldaia nel codice stesso, Visual Studio 2015 ora include un'opzione di refactoring per generare automaticamente la piastra di caldaia per te.

Per usarlo, crea prima l'interfaccia IExamplee l'implementazione Example. Quindi crea la tua nuova classe Compositee rendila ereditaria IExample, ma non implementare l'interfaccia.

Aggiungi una proprietà o un campo di tipo Examplealla tua Compositeclasse, apri il menu di azioni rapide sul token IExamplenel tuo Compositefile di classe e seleziona "Implementa l'interfaccia tramite" Esempio "" dove "Esempio" in questo caso è il nome del campo o della proprietà.

Si noti che mentre Visual Studio genererà e reindirizzerà tutti i metodi e le proprietà definiti nell'interfaccia su questa classe di supporto, non reindirizzerà gli eventi dal momento in cui questa risposta è stata pubblicata.


1

La tua classe sta facendo troppo, è per questo che devi implementare così tanto boilerplate. Dici nei commenti:

sembra più sensato scrivere Person.Walk () rispetto a Person.WalkHelper.Walk ()

Non sono d'accordo. L'atto di camminare probabilmente implica molte regole e non appartiene alla Personclasse - appartiene a una WalkingServiceclasse con un walk(IWalkable)metodo in cui la Persona implementa IWalkable.

Tieni sempre presente i principi SOLIDI quando scrivi il codice. I due che sono più applicabili qui sono Separazione delle preoccupazioni (estrai il codice ambulante in una classe separata) e Segregazione delle interfacce (suddivide le interfacce in compiti / funzioni / abilità specifici). Sembra che potresti avere un po 'dell'Io con le tue interfacce multiple, ma stai quindi annullando tutto il tuo buon lavoro facendo implementare una classe.


Nel mio esempio la funzionalità è implementata in classi separate e ho una classe composta da alcune di queste come un modo per raggruppare il comportamento richiesto. Nessuna implementazione effettiva esiste nella mia classe più ampia, è gestita dai componenti più piccoli. Forse hai ragione, però, e rendere pubbliche quelle classi di supporto in modo che possano interagire direttamente è la strada da percorrere.
Nick Udell,

4
Per quanto riguarda l'idea di un walk(IWalkable)metodo, personalmente non mi piace perché i servizi di camminata diventano responsabilità del chiamante, e non della Persona, e la coerenza non è garantita. Ogni chiamante dovrebbe sapere quale servizio utilizzare per garantire la coerenza, mentre tenerlo nella classe Persona significa che deve essere modificato manualmente affinché il comportamento del camminare differisca, consentendo comunque l'inversione della dipendenza.
Nick Udell,

0

Quello che stai cercando è l'eredità multipla. Tuttavia, né C # né Java ce l'hanno.

Puoi estendere B da A. Questo eliminerà la necessità di un campo privato per B così come la colla di reindirizzamento. Ma puoi farlo solo per uno di B, C o D.

Ora, se riesci a far ereditare C da B, e D da C, devi solo avere A estendere D ...;) - solo per essere chiari, in realtà non lo sto incoraggiando in questo esempio poiché non ci sono indicazioni per questo.

In C ++ potresti avere A ereditato da B, C e D.

Ma l'ereditarietà multipla rende i programmi più difficili da ragionare e ha i suoi problemi; Inoltre, c'è spesso un modo pulito per evitarlo. Tuttavia, a volte senza di essa, è necessario effettuare compromessi di progettazione tra la ripetizione della piastra di caldaia e il modo in cui viene esposto il modello a oggetti.

È un po 'come se stessi provando a mescolare composizione ed eredità. Se fosse C ++, potresti usare una soluzione di eredità pura. Ma dato che non lo è, consiglierei di abbracciare la composizione.


2
Puoi avere ereditarietà multiple usando le interfacce sia in java che in c #, proprio come ha fatto op nel suo codice di esempio.
Robert Harvey,

1
Alcuni potrebbero considerare di implementare un'interfaccia come l'ereditarietà multipla (come in C ++). Altri non sarebbero d'accordo, perché le interfacce non possono presentare metodi di istanze ereditabili, che sarebbe quello che avresti se avessi ereditarietà multiple. In C #, non è possibile estendere più classi di base, pertanto non è possibile ereditare le implementazioni del metodo di istanza da più classi, motivo per cui è necessario tutto il boilerplate ...
Erik Eidt,

Concordo sul fatto che l'ereditarietà multipla sia negativa, ma l'alternativa di C # / Java all'ereditarietà singola con l'implementazione di più interfacce non è una cura completa (senza inoltro di interfaccia è un incubo ergonomico). Dopo aver trascorso un po 'di tempo con Golang, penso che Go abbia avuto l'idea giusta di non avere alcuna eredità e invece di affidarsi interamente alla composizione con l'inoltro di interfaccia, ma anche la loro particolare implementazione ha dei problemi. Penso che la soluzione migliore (che nessuno sembra aver ancora implementato) sia la composizione, con l'inoltro, con la possibilità di utilizzare qualsiasi tipo come interfaccia.
Dai
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.