Due definizioni contraddittorie del principio di segregazione dell'interfaccia - quale è corretto?

13

Durante la lettura di articoli su ISP, sembrano esserci due definizioni contraddittorie di ISP:

Secondo la prima definizione (vedi 1 , 2 , 3 ), l'ISP afferma che le classi che implementano l'interfaccia non dovrebbero essere costrette ad implementare funzionalità di cui non hanno bisogno. Quindi, fat interface IFat

interface IFat
{
     void A();
     void B();
     void C();
     void D();
}

class MyClass: IFat
{ ... }

dovrebbe essere diviso in interfacce più piccole ISmall_1 e ISmall_2

interface ISmall_1
{
     void A();
     void B();
}

interface ISmall_2
{
     void C();
     void D();
}

class MyClass:ISmall_2
{ ... }

in questo modo il mio MyClass è in grado di implementare solo i metodi di cui ha bisogno ( D() e C() ), senza essere costretti a fornire anche implementazioni fittizie per A() , B() e C() :

Ma secondo la seconda definizione (vedi 1 , 2 , risposta di Nazar Merza ), ISP afferma che MyClient chiama i metodi su MyService shouldn ' t essere a conoscenza dei metodi su MyService di cui non ha bisogno. In altre parole, se MyClient richiede solo la funzionalità di C() e D() , allora invece di

class MyService 
{
    public void A();
    public void B();
    public void C();
    public void D();
}

/*client code*/      
MyService service = ...;
service.C(); 
service.D();

dovremmo separare i metodi MyService's in interfacce specifiche del client :

public interface ISmall_1
{
     void A();
     void B();
}

public interface ISmall_2
{
     void C();
     void D();
}

class MyService:ISmall_1, ISmall_2 
{ ... }

/*client code*/
ISmall_2 service = ...;
service.C(); 
service.D();

Quindi con la precedente definizione, l'obiettivo dell'ISP è " rendere più facile la vita delle classi che implementano l'interfaccia IFat ", mentre con quest'ultimo l'obiettivo dell'ISP è di " vita dei client che chiamano i metodi di MyService più semplici ".

Quale delle due diverse definizioni di ISP è effettivamente corretta?

@MARJAN VENEMA

1.

So when you are going to split IFat into smaller interface, which methods end up in which ISmallinterface should be decided based on how cohesive the members are.

Anche se è logico mettere metodi coesivi all'interno della stessa interfaccia, ho pensato con il modello ISP che le esigenze del cliente hanno la precedenza sulla "coesione" di un'interfaccia. In altre parole, pensavo che con l'ISP dovremmo raggruppare all'interno della stessa interfaccia quei metodi necessari a particolari client, anche se ciò significa lasciare fuori da quell'interfaccia quei metodi che dovrebbero, per motivi di coesione, essere inseriti all'interno della stessa interfaccia?

Quindi, se ci sono molti clienti che hanno solo bisogno di chiamare CutGreens , ma non anche GrillMeat , quindi per aderire al pattern ISP dovremmo solo mettere CutGreens dentro ICook , ma non anche GrillMeat , anche se i due metodi sono altamente coesivi ?!

2.

I think that your confusion stems from the a hidden assumption in the first definition: that the implementing classes are already following the single responsibility principle.

Con "implementare le classi che non seguono SRP" ti riferisci a quelle classi che implementano IFat o alle classi che implementano ISmall_1 / ISmall_2 ? Presumo che ti riferisci alle classi che implementano IFat ? In tal caso, perché ritieni che non seguano già SRP?

grazie

    
posta EdvRusj 21.06.2013 - 18:41
fonte

3 risposte

5

Entrambi sono corretti

Il modo in cui l'ho letto, lo scopo dell'ISP (Interfaccia Segregation Principle) è quello di mantenere le interfacce piccole e focalizzate: tutti i membri dell'interfaccia dovrebbero avere una coesione molto alta. Entrambe le definizioni hanno lo scopo di evitare interfacce "jack-of-all-trades-master-of-none".

L'interfaccia segregazione e SRP (principio di singola responsabilità) hanno lo stesso obiettivo: garantire componenti software piccoli e altamente coesi. Si completano a vicenda. La segregazione dell'interfaccia garantisce che le interfacce siano piccole, focalizzate e altamente coese. Seguendo il principio della responsabilità unica, le classi sono piccole, focalizzate e altamente coese.

La prima definizione che menzioni si concentra sugli implementatori, la seconda sui clienti. Che, contrariamente a @ user61852, considero gli utenti / i chiamanti dell'interfaccia, non gli implementatori.

Penso che la tua confusione derivi da un'ipotesi nascosta nella prima definizione: che le classi di implementazione stanno già seguendo il principio della responsabilità unica.

Per me la seconda definizione, con i client come i chiamanti dell'interfaccia, è un modo migliore per raggiungere l'obiettivo prefissato.

segregare

Nella tua domanda dichiari:

since this way my MyClass is able to implement only the methods it needs ( D() and C() ), without being forced to also provide dummy implementations for A(), B() and C():

Ma questo sta ribaltando il mondo.

  • Una classe che implementa un'interfaccia non impone ciò di cui ha bisogno nell'interfaccia che sta implementando.
  • Le interfacce dettano i metodi che una classe di implementazione dovrebbe fornire.
  • I chiamanti di un'interfaccia sono quelli che dettano quale funzionalità hanno bisogno dell'interfaccia per fornire loro e quindi cosa dovrebbe fornire un implementatore.

Quindi, quando dividi IFat nell'interfaccia più piccola, quali metodi finiscono in cui l'interfaccia ISmall deve essere decisa in base alla coesione dei membri.

Considera questa interfaccia:

interface IEverythingButTheKitchenSink
{
     void DoDishes();
     void CleanSink();
     void CutGreens();
     void GrillMeat();
}

Quali metodi inseriresti in ICook e perché? Metteresti CleanSink insieme a GrillMeat solo perché ti capita di avere una classe che fa proprio questo e un paio di altre cose, ma niente come gli altri metodi? Oppure lo divideresti in due interfacce più coese, come ad esempio:

interface IClean
{
     void DoDishes();
     void CleanSink();
}

interface ICook
{
     void CutGreens();
     void GrillMeat();
}

Nota sulla dichiarazione dell'interfaccia

Una definizione di interfaccia dovrebbe preferibilmente essere autonoma in un'unità separata, ma se deve assolutamente convivere con il chiamante o l'implementatore, dovrebbe essere davvero con il chiamante. In caso contrario, il chiamante ottiene un'immediata dipendenza dall'implementatore che sta completamente distruggendo lo scopo delle interfacce. Vedi anche: dichiarare l'interfaccia nello stesso file della classe base, è una buona pratica? sui programmatori e < a href="https://stackoverflow.com/q/5840219/11225"> Perché dovremmo posizionare le interfacce con le classi che le usano piuttosto che quelle che le implementano? su StackOverflow.

    
risposta data 21.06.2013 - 21:38
fonte
13

Confondi la parola "client" come usata nei documenti della Gang dei quattro con un "cliente" come in un consumatore di un servizio.

Un "client", come previsto dalle definizioni di Gang of Four, è una classe che implementa un'interfaccia. Se la classe A implementa l'interfaccia B, allora dicono che A è un client di B. Altrimenti la frase "i client non dovrebbero essere forzati ad implementare interfacce che non usano" non avrebbe senso dal momento che "i client "(come nei consumatori) non implementano nulla. La frase ha senso solo quando vedi "client" come "implementatore".

Se "client" significa una classe che "consuma" (chiama) i metodi di un'altra classe che implementa la grande interfaccia, quindi chiamando i due metodi che ti interessano e ignorando il resto, sarebbe sufficiente per tenerti disaccoppiato da il resto dei metodi che non usi.

Lo spirito del principio è evitare che il "client" (la classe che implementa l'interfaccia) debba implementare metodi fittizi per rispettare l'intera interfaccia quando si preoccupa solo di un insieme di metodi correlati.

Inoltre, mira ad avere la minore quantità di accoppiamento possibile in modo che le modifiche apportate in un punto causino il minore impatto. Separando le interfacce si riduce l'accoppiamento.

Questi problemi appaiono quando l'interfaccia fa troppo e hanno metodi che dovrebbero essere divisi in diverse interfacce invece di una sola.

Entrambi i tuoi esempi di codice sono OK . È solo che nel secondo si assume che "client" significa "una classe che consuma / chiama i servizi / metodi offerti da un'altra classe".

Non trovo contraddizioni nei concetti spiegati nei tre link che hai fornito.

Tieni semplicemente chiaro che "client" è implementatore , in SOLID talk.

    
risposta data 21.06.2013 - 19:44
fonte
5

L'ISP si basa sull'isolamento del client dal sapere di più sul servizio di quanto non debba sapere (proteggendolo da modifiche non correlate, ad esempio). La tua seconda definizione è corretta. Alla mia lettura, solo uno di questi tre articoli suggerisce il contrario ( il primo ) ed è semplicemente sbagliato. (Modifica: No, non sbagliato, solo fuorviante.)

La prima definizione è molto più strettamente legata a LSP.

    
risposta data 21.06.2013 - 18:44
fonte

Leggi altre domande sui tag