Responsabilità singola: in che modo questo codice causa problemi?

0

Data la seguente interfaccia

interface Modem {
    public void Dial(string pno);
    public void Hangup();
    public void Send(char c);
    public char Recv();
}

Uncle Bob scrive

However, there are two responsibilities being shown here. The first responsibility is connection management. The second is data communication.

...

Should these two responsibilities be separated? That depends upon how the application is changing. If the application changes in ways that affect the signature of the connection functions, then the design will smell of Rigidity because the classes that call send and read will have to be recompiled and redeployed more often than we like.

Qualcuno può spiegarmi come è vera la frase evidenziata? Perché dovrei ricompilare il codice che chiama i metodi di invio / ricezione se la firma della connessione cambia? Potrei aver iniettato una connessione già pronta al codice usando questo.

    
posta dsplynm 01.09.2014 - 10:07
fonte

2 risposte

7

Diciamo che per qualche motivo vorremmo estendere il metodo Dial per accettare una struttura

struct Endpoint
{
    public string pno;
    ...
}

class Modem
{
    public void Dial(Endpoint ep);
    ...
}

Ciò causerebbe la ricompilazione dell'intera classe, a seconda della lingua e del compilatore, quindi Send e Recv. Se si aderisce all'SRP, ad esempio, è possibile modificare la classe del modem in

class Modem
{
    void Dial(Endpoint ep);
    void Hangup();
    Stream ConnectionStream();
}

con un flusso che è responsabile per l'invio e la ricezione di dati. Questo evita di ricompilare Send e Recv ogni volta che Modem cambia, ma solo il - molto semplice - metodo che restituisce il nostro stream.

Comunque, penso che il principale vantaggio dell'SRP non sia il fatto che in alcune circostanze non venga ricompilato alcun codice - direi che non ha molta importanza con la potenza computazionale disponibile al giorno d'oggi - ma più pulito e più codice conciso.

    
risposta data 01.09.2014 - 10:30
fonte
3

Penso che la parte peggiore dell'interfaccia sia il fatto che esiste un accoppiamento temporale.

Ci si aspetta che i client dell'interfaccia chiamino i metodi nell'ordine corretto, e questo ordine non è applicato dal sistema dei tipi, quindi può essere dedotto solo da prove ed errori o dalla conoscenza del dominio preesistente.

Il mio pensiero iniziale è di mantenere un'interfaccia separata e pulita per la connessione che può essere utilizzata per inviare materiale:

public interface IConnection // don't pay too close attention to the name, there are better
{
    void Send(char c);
    char Recieve();
}

Voglio che il codice client che usa la connessione semplicemente faccia cose del tipo:

using (var connection = probablyAnInjectedFactory.Dial(someNumber))
{
    // do stuff with the connection
}

Stuff come le connessioni di apertura e chiusura in realtà dovrebbero essere incapsulate nel tipo di implementazione che penso. Una connessione potrebbe essere aperta nel costruttore (tramite alcune dipendenze iniettate) o da una fabbrica. Chiudere la connessione è bello da catturare nel pattern IDisposable (penso che si chiami AutoCloseable o qualcosa del genere in Java?)

Ovviamente è necessario apportare alcune modifiche a seconda del progetto e dei requisiti. Forse le istanze di connessione devono essere riutilizzabili o raggruppate, ecc. Il punto principale che sto cercando di trasmettere è: Se stai lavorando in una lingua tipizzata staticamente, USERLA. Rendetelo LETTERALMENTE IMPOSSIBILE per compilare codice con bug (sì, so che è impossibile, intendo come obiettivo asintotico). Fai il modo giusto per usare i tuoi tipi l'UNICO modo per usarli.

Alla fine, ciò consente di risparmiare ore infinite per gli sviluppatori che non hanno più bisogno di cercare nella documentazione scarsa o deprecata per capire in quale ordine magico chiamare i metodi. In questo caso, penso che sia una cosa molto più preziosa della rigida definizione di SRP di Uncle Bob.

    
risposta data 30.06.2016 - 12:58
fonte