Creazione di un'interfaccia di interfacce

4

Sfondo:

Abbiamo una classe che ascolta su un socket e imposta valori su se stessa in base a ciò che legge fuori dal socket. Credo che aderisca a SRP.

Per aderire all'ISP abbiamo creato un'interfaccia per il socket (ad esempio StartListening , StopListening ) e un'altra interfaccia che contiene campi di sola lettura per i valori letti dal socket e modificato la nostra classe per implementarli entrambi.

L'idea era che abbiamo passato ISocketStuff (non il suo vero nome) al codice che ha bisogno di avviare / interrompere l'ascolto e passiamo ISocketValues a luoghi che hanno bisogno di conoscere i valori.

La domanda:

Stavo rivedendo il codice di un collega e lui aveva qualcosa del tipo:

public interface ISocketStuffAndValues : ISocketStuff, ISocketValues {};

E lo stava trasmettendo a un costruttore.

Questo mi sembrava sbagliato, ma non riuscivo a pensare a nessuna ragione per non farlo. Doveva risolvere un problema di design spinoso, poteva essere appena passato in ISocketValues , ma voleva farlo e non potevo dirgli perché era una scelta sbagliata.

Quindi, è una scelta sbagliata? Quali motivi avrei potuto dare per passare solo in ISocketValues e non creare ISocketStuffAndValues ?

    
posta Robert Gowland 30.05.2013 - 16:40
fonte

1 risposta

6

Il tuo collega stava cercando di ridurre un altro odore di codice; avere molti parametri e / o ridondanti nelle chiamate di metodo. Se aderisci a ISP, SRP e DIP consentendo l'implementazione di diverse implementazioni delle due interfacce in un metodo che richiede un'implementazione di ciascuna, ma hai una classe che implementa entrambe le interfacce, quando chiamerai questo metodo sarai passando la stessa istanza due volte, una volta come ISocketStuff e una volta come ISocketValues. Il tuo collega ha pensato che puzzava male (e ha ragione) e ha effettuato il refactoring per consentire un singolo riferimento passato.

È sempre una buona idea, invece di seguire ciecamente le regole SOLID, per ricordare a te stesso perché esistono queste regole; qual è il loro scopo? nel caso di ISP, lo scopo della regola è di evitare di dover apportare modifiche a un consumatore di un'interfaccia che non consuma il particolare metodo di tale interfaccia che viene modificato. Tuttavia, se tutti gli utilizzi hanno bisogno di tutti i metodi, è chiaro che i metodi dell'interfaccia sono coerenti, almeno per quanto riguarda il sistema, e non è necessario separarli.

La risposta giusta alla tua domanda dipende quindi dalla risposta a questa domanda: ci si aspetta che tutti i consumatori di un'implementazione di ISocketStuff abbiano bisogno anche di ISocketValues?

Se è così, allora la tua affermazione che queste devono essere interfacce separate è infondata. Se ogni implementazione di uno implementa l'altro e ogni consumatore ne consuma entrambi, se cambiano le interfacce, tutte le implementazioni e gli usi delle interfacce both cambieranno e quindi non guadagnerai nulla nel separarli. I membri di entrambe le interfacce sarebbero altamente coesivi in un'unica interfaccia. L'odore del codice di ricongiungere entrambe le interfacce in un'interfaccia child utilizzata dalle implementazioni e dai consumatori è la tua bandiera rossa che hai troppo segregato date le esigenze del tuo sistema, e ISocketStuff dovrebbe semplicemente esporre i membri di ISocketValues.

Se no, allora hai altri problemi. Non è male, di per sé, per un oggetto implementare più interfacce; questo è parte del perché le interfacce esistono e perché l'implementazione di molte di esse è consentita dalle specifiche della lingua in primo luogo. Tuttavia, in questo caso, un oggetto con la sola responsabilità dell'ascolto del socket e un oggetto con la sola responsabilità di memorizzare i valori di stato / dati sono due oggetti con due responsabilità e non dovrebbero avere un'unica implementazione condivisa. Invece, considera di rendere l'implementazione di ISocketValues una classe di dati "thin" (un DTO), che viene prodotto da un metodo esposto da ISocketStuff.

ISocketValues potrebbe essere economico e usa e getta, e ne ottieni uno ogni volta che hai bisogno di un aggiornamento e poi lo butti via, o in alternativa le istanze di ISocketValues potrebbero essere più longevi e ricevere aggiornamenti "live" da ISocketStuff. Il che è meglio dipende da come preferisci utilizzare entrambe le interfacce e da quale delle interfacce vuoi che altri oggetti abbiano a conoscenza. Se la maggior parte o tutti i consumatori hanno bisogno di controllare l'ascoltatore in base agli aggiornamenti, allora hanno bisogno di ISocketStuff; rendere l'implementazione di ISocketValues un DTO, esporre un metodo factory per generarli e passare ISocketStuff ai consumatori, che possono generare istanze ISocketValues secondo necessità. Se tutti i tuoi consumatori hanno bisogno di informazioni su dati / stato, ISocketStuff ha troppa energia; il proprietario di ISocketStuff che sta iniettando queste dipendenze nei chiamanti dovrebbe generare e passare implementazioni di ISocketValues "live-update" ai consumatori, che non dovranno più sapere che ISocketStuff esiste anche.

    
risposta data 30.05.2013 - 18:15
fonte

Leggi altre domande sui tag