Trattare con una grande interfaccia

4

Sto lavorando su un programma che risolve un certo tipo di sistemi di equazioni. Gli oggetti dati principali sono Equazione, Variabile, Soluzione. Quindi ho questa interfaccia, che rappresenta tutte le cose che voglio esporre al livello dell'interfaccia utente.

public interface ISystemSolver
{
    // Configuration
    int NumberBase { get; set; }
    bool CaseSensitive { get; set; }
    bool LeadingZeros { get; set; }

    // State
    IReadOnlyList<int> ForbiddenValues { get; }
    IReadOnlyList<char> ForbiddenVariables { get; }
    event EventHandler OnTooManyVariables;
    bool AreThereTooManyVariables { get; }

    // Variables
    IEnumerable<Variable> Variables { get; }
    void RemoveVariable(int index);
    void AddVariable(char variable, int value);

    // Equations
    IEnumerable<Equation> Equations { get; }
    void RemoveEquation(int index);
    void AddEquation(string equation);

    // Solving
    IEnumerable<Solution> Solve();
}

La classe che la implementa violerebbe certamente il singolo principio responsabile. Come posso evitare questo?

Ho avuto questa idea: estrai aree particolari nell'interfaccia. Ad esempio, i 3 metodi che trattano le variabili formerebbero un'interfaccia chiamata IVariables .

Ma allora, non sarebbe una violazione della legge di Demeter quando ho bisogno di aggiungere una variabile? Perché dovrei chiamare SystemSolver.Variables.AddVariable(...)

    
posta Patrik Bak 05.08.2017 - 00:01
fonte

1 risposta

9

Prima di tutto, grazie per aver pensato al tuo design e cercando di seguire alcuni principi. Non mi piace passare il codice prodotto da quelli che non lo fanno.

Legge di Demetra

Lasciatemi demistificare la Legge di Demeter per te. Non è un esercizio di conteggio dei punti.

SystemSolver.Variables.AddVariable(...);

Questo è lo stesso di questo:

Variables vars = SystemSolver.Variables;
vars.AddVariable(...);

In ogni caso, questo può essere buono o cattivo a seconda delle relazioni qui. Se SystemSolver e Variables sono amici intimi che sono stati intenzionalmente ed esplicitamente progettati per essere usati in questo modo, ad esempio con un linguaggio specifico di dominio che sta cercando di farti costruire qualcosa, allora sicuramente ti butteranno fuori. Lo stai usando come previsto e questo può essere molto potente ed espressivo.

Ma se stai afferrando roba a caso, perché ti capita di trovarla in questo modo stai facendo il pieno di colla su tutto il lego. Cioè, stai decidendo che queste classi separate devono avere una relazione intima e inamovibile. Prendere questa decisione non dovrebbe essere responsabilità dei clienti che utilizzano tali classi. Questo dovrebbe spettare al progettista di quelle classi. Se lo fai, e qualcuno decide di cambiare la relazione, non piangere perché il tuo cliente si è rotto. Non ti è mai stato promesso che questo avrebbe continuato a funzionare.

Potrei andare avanti ma ho parlato di Demeter prima.

Iniezione di dipendenza

Se non vuoi creare una relazione così intima come hai intenzione di ottenere ciò di cui hai bisogno? Hai lasciato che fosse consegnato a te.

SystemSolver ss = new SystemSolver();
Variables vars = new Variables();
UI ui = new UI(ss, vars);

Lì, ora l'interfaccia utente sa di cosa ha bisogno ma non si conoscono nemmeno l'uno dell'altro. Ha senso? Forse le Variabili sono prive di significato senza che SystemSolver le faccia. Se è così allora forse hanno una relazione intima. Se è così, allora progettali in questo modo in modo da individuare ciò di cui hai bisogno non è una violazione di Demetra.

Principio di responsabilità singola

Quando qualcosa ha una sola responsabilità, dovrebbe avere solo una ragione per cambiare. Ciò non significa che quando guardi dentro di te trovi solo una cosa. Significa che tutto ciò che trovi all'interno è focalizzato sulla stessa cosa.

Pensa alla tua auto. Quasi tutto nella tua auto è una cosa da macchina. I posti sono posti auto. I pneumatici sono pneumatici per auto. Il cruscotto è un cruscotto dell'auto. La tazza di caffè è una, aspetta cosa sta facendo qui? Mentre un sacco di cose possono stare in una macchina, alcune cose non sono cose che vuoi trovare lì quando acquisti la macchina.

Il cambio della tazza non dovrebbe influenzare la macchina. Cambiare l'auto non dovrebbe influire sulla tazza. Al massimo le modifiche a entrambi dovrebbero avere effetto solo sul portalattine. Ecco perché ci interessa davvero seguire SRP. Limita la portata del cambiamento. Ho anche ne ho parlato prima quindi mi fermo qui. (Man sono stato su questo sito per tanto tempo?)

Perché non stai violando SRP

"[ ISystemSolver ] rappresenta tutte le cose che voglio esporre al livello dell'interfaccia utente."

Questa è una singola responsabilità. È grande. Ma è ancora single. Volete suddividerlo, il che è positivo, ma SRP non è il perché.

I suoi metodi sono suddivisi in 5 gruppi diversi, ognuno dei quali riguarda una sola idea. Questo per me segnala 5 responsabilità. È così male? No. Queste responsabilità sono tutte incentrate sul fornire all'interfaccia utente ciò di cui ha bisogno.

Ma quei 5 dovrebbero essere raggruppati insieme. Questo mi ricorda

The Facade Pattern

Ciascuno di questi 5 gruppi potrebbe essere una classe separata. Gli oggetti di cui sono aggiunti alla classe di facciata dell'interfaccia utente che delega il lavoro a ciascuno di essi. Ciò consente alle cose di separarsi ma mantiene la complessità della struttura nascosta dall'interfaccia utente. Inoltre, renderebbe la Legge di Demetra qualcosa di cui non dovrai più preoccuparti perché tutto ciò di cui hai bisogno è proprio lì.

Dillo, non chiedere

Stai usando molti getter e setter. Comprendere che questi rompono l'incapsulamento e portano a scrivere codice procedurale. È molto più potente dire agli oggetti cosa fare e lasciare che decidano come farlo piuttosto che chiedere loro qualcosa e prendere una decisione in base a ciò che hai appreso. Vorrei aver scritto questo ma sono contento di averlo letto.

    
risposta data 05.08.2017 - 04:33
fonte