C # ha un modo di usare un'istanza dichiarata multi-interfaccia, o qualcosa di simile quando si applica l'ISP?

3

La mia comprensione dell'ISP è che il "client" nella definizione (vedi sotto) può significare una classe che implementa l'interfaccia o un'istanza dichiarata con 1 delle varie opzioni di interfaccia, in base al client menzionato nella definizione di Wikipedia:

The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use. [source]

Ho visto alcuni post di SE che dicono che è quando l'istanza è usata , e altri che dicono < a href="https://softwareengineering.stackexchange.com/a/202331/202669"> quando l'interfaccia è implementata . Secondo questa risposta SE può essere considerata entrambe. Quindi continuerò sulla domanda tenendo presente che può essere o.

Diciamo che per la particolare architettura che sto costruendo avrà 1 classe che implementa tutte le interfacce, ma voglio che l'istanza sia guardata in modo flessibile quando l'istanza è usata.

public interface IAutoToggle
{
    IEnumerable<IToggleable> Toggle(IAttribute attribute);
}

public interface IManualToggle
{
    void Toggle(IEnumerable<IToggleable> toggles);
}

public interface IMixedToggle: IAutoToggle, IManualToggle {}

public class Toggler : IMixedToggle
{
    //...implementations
}

Ora, quando è istanziato, si può decidere se eseguire 1 dei 2 o entrambi:

IMixedToggler mixedToggler = new Toggler();
//or
IAutoToggler autoToggler = new Toggler();
//or
IManual manualToggler = new Toggler();

Questo modo di fare le cose segue ancora il principio di separazione delle interfacce?

Se lo fa, c'è un modo per fare lo stesso effetto in C # senza avere quell'interfaccia ereditata extra? Ad esempio Objective-C, essendo un linguaggio più dinamico ti permette di fare qualcosa di simile se non sbaglio:

id<IAutoToggle, IManualToggle> toggler = [[Toggler alloc] init];

C'è un modo per evitare l'ulteriore interfaccia IMixedToggle in C # (come fatto sopra in Objective-C)? O ci sono dei buoni motivi per non seguire il percorso che sto facendo?

    
posta morbidhawk 10.04.2017 - 18:37
fonte

3 risposte

5

Does this way of doing things still follow the Interface-segregation principle?

Questo va bene e non infrange l'ISP.

is there a way to do this same effect in C# without having that extra inherited interface?

Non ne hai bisogno. Invece di

public interface IMixedToggle: IAutoToggle, IManualToggle {}

public class Toggler : IMixedToggle
{
    //...implementations
}

puoi semplicemente scrivere

public class Toggler : IAutoToggle, IManualToggle
{
    //...implementations
}

Quando usi la classe, a seconda dell'interfaccia di cui hai bisogno, la istanziata come

IAutoToggle o = container.Resolve<IAutoToggle>();

o

IManualToggle o = container.Resolve<IManualToggle>();

A seconda di come è stato configurato il vostro stabilimento, è possibile che la fabbrica restituisca la stessa istanza concreta a entrambe le chiamate. Ma non devo , perché stiamo seguendo l'ISP.

Ora se hai un pezzo di codice che in realtà dipende dal fatto che lo stesso oggetto implementa entrambe le interfacce contemporaneamente ... beh ... che violerebbe l'ISP. La fabbrica dovrebbe essere in grado di fornire qualsiasi oggetto vecchio che implementa l'interfaccia senza influire sul codice chiamante.

    
risposta data 10.04.2017 - 19:02
fonte
0

The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use

Penso che la definizione sopra significhi

no client should be forced to implement methods it does not use

Va bene che il consumatore dell'interfaccia dipenda dall'interfaccia con più metodi, ma ne usi solo uno.

Quindi nel tuo caso puoi introdurre solo un'interfaccia

public interface IToggler
{
    IEnumerable<IToggleable> ToggleByAttribute(IAttribute attribute);

    void Toggle(IEnumerable<IToggleable> toggles);
}

E implementazione

public interface Toggler : IToggler
{
    IEnumerable<IToggleable> ToggleByAttribute(IAttribute attribute) 
    { 
        // Implementation
    }

    void Toggle(IEnumerable<IToggleable> toggles)
    { 
        // Implementation
    }
}

Puoi considerare l'applicazione del principio di segregazione dell'interfaccia solo nei casi in cui decidi di avere più di un'implementazione per uno dei metodi.

    
risposta data 24.04.2017 - 11:01
fonte
0

Ho ricevuto ottimi riscontri dalle altre risposte al momento in cui ho postato questa domanda. Oggi mi è tornato in mente questo scenario e ho pensato a un paio di modi specifici in cui questo può essere gestito, e me ne sto rendendo conto ora sembra essere molto in linea con le risposte che ho ricevuto prima. Per spiegare questo ho bisogno di fare una distinzione tra l'implementatore e l'istanziatore.

Instantiator (codice esterno) vs Implementor (codice interno)

Mi riferirò all'implementatore della classe Toggler come implementatore e al codice che lo usa e ad un certo punto lo istanzia come instantiator .

L'implementazione della classe Toggler non dovrebbe essere eccessivamente preoccupata per tutte le combinazioni di modi in cui può essere utilizzata dal codice che la istanzia. Dovrebbe solo fare ciò che ha senso in relazione alla sua implementazione di classe. Spetta all'istanziatore decidere come verrà utilizzato. L'implementatore dovrebbe probabilmente essere consapevole della sua interfaccia e dovrebbe essere pulito / semplice, e come tale sa come può essere usato, ma non è responsabile per tutti i modi in cui sarà usato.

Opzione 1: se separio le interfacce

Diciamo che è implementato come tale:

public class Toggler : IAutoToggler, IManualToggler { ... }

Ora a questo punto possiamo già fare facilmente IAutoToggler autoToggler = new Toggler(); per programmare solo sull'interfaccia di commutazione automatica e lo stesso per la commutazione manuale. Ma la preoccupazione ora è se io volessi entrambi? Questa responsabilità ricade sull'istantaneo. Come menzionato nella risposta di John Wu, questo può essere gestito da un contenitore IoC e con l'iniezione delle dipendenze un modo potrebbe essere quello di iniettare entrambe le interfacce in un costruttore di classi e utilizzare le interfacce separatamente in una singola classe che lo avvolge.

public class TogglerHandler
{
    private readonly IAutoToggler _autoToggler;
    private readonly IManualToggler _ toggler;
    public TogglerHandler(IAutoToggler autoToggler, IManualToggler toggler) {...}

    // exposing access to both underlying interfaces here:
    public IEnumerable<IToggleable> Toggle(IAttribute attribute) {...}
    public void Toggle(IEnumerable<IToggleable> toggles) {...}
}

Un altro modo per farlo è sottoclasse la classe Toggler e fornire la tua interfaccia:

public interface IMixedToggler : IAutoToggler, IManualToggler {}
public class MixedToggler : Toggler, IMixedToggler 
{
}

// somewhere using the code now can use a mixed toggling interface
IMixedToggler toggler = new MixedToggler();

Opzione 2: se creo un'interfaccia singola

Fabio ha detto che ha senso usare una singola interfaccia.

Diciamo che l'implementazione è simile a questa e ha i metodi di azione precedentemente in una singola interfaccia:

public interface IToggler
{
    IEnumerable<IToggleable> ToggleByAttribute(IAttribute attribute);
    void Toggle(IEnumerable<IToggleable> toggles);
}
public class Toggler : IToggler { ... }

Ora l'istanziatore o l'utente di questa interfaccia potrebbe non aver bisogno dell'interfaccia completa. Questa responsabilità di esporre solo ciò che è necessario ricade sull'istanza che utilizzerà l'implementazione di IToggler. Un modo possibile è di riavvolgerlo di nuovo, quindi diciamo che è necessaria solo la commutazione automatica:

public interface IAutoToggler
{
    IEnumerable<IToggleable> Toggle(IAttribute attribute)
}

public class AutoToggler : Toggler, IAutoToggler 
{
    ...
}

// somewhere using the code now can only use auto-toggler interface
IAutoToggler autoToggler = new AutoToggler();

Conclusione

Ciò che ho concluso è che non importa se ho separato l'interfaccia o l'ho mantenuta una sola, è ancora possibile esporla o limitarne l'accesso via ereditarietà o avvolgendo il codice. La ragione per cui ho avuto difficoltà a vederlo prima è perché ero sia colui che implementa quella classe sia colui che la crea, ma forse fare questa distinzione è utile per prendere questo tipo di decisioni.

Quando si progetta l'interfaccia di un'implementazione, fare in modo che abbia più senso per il punto di vista dell'implementazione interna, e per il codice esterno chiamarlo o usarlo così com'è se ti dà accesso a ciò di cui hai bisogno o modificarlo a una forma che ha più senso interagire con.

In risposta alla mia domanda iniziale, l'interfaccia IMixedToggler non avrebbe dovuto essere creata / implementata. L'unico scopo sarebbe avere un ulteriore modo di interfacciare con la classe. Questo dovrebbe essere stato calcolato dal codice che l'istanza / utilizza come responsabile che è caduto là nel codice esterno.

Nota a margine

Questo estratto di codice proviene da Http Parser di Kestrel:

public class HttpParser<TRequestHandler> 
    : IHttpParser<TRequestHandler> 
    where TRequestHandler 
        : IHttpHeadersHandler, 
          IHttpRequestLineHandler
{
   ...
}

Questo esempio in realtà mi ha portato a concludere che l'implementazione interna non dovrebbe dettare il modo in cui verrà utilizzata. HttpParseObject non può essere inizializzato senza che esista un'interfaccia esistente sia IHttpHeadersHandler che IHttpRequestLineHandler. Quindi, anche per inizializzare questa cosa è necessario avere un'interfaccia che non è nemmeno specificata direttamente qui, dice solo che ha bisogno di un'interfaccia che eredita da entrambi. Quindi, in sostanza, sta applicando che l'instantiator crea una nuova interfaccia conforme a ciò che vuole:

interface IFoo: IHttpHeadersHandler, IHttpRequestLineHandler { }
var parser = new HttpParser<IFoo>(true);
    
risposta data 22.07.2017 - 17:15
fonte

Leggi altre domande sui tag