Ecco un problema che mi capita spesso di incontrare: lasciare che ci sia un progetto di negozio web che abbia una classe di prodotto. Voglio aggiungere una funzione che consente agli utenti di pubblicare recensioni su un prodotto. Quindi ho una classe Review che fa riferimento a un prodotto. Ora ho bisogno di un metodo che elenca tutte le recensioni su un prodotto. Esistono due possibilità:
(UN)
public class Product {
...
public Collection<Review> getReviews() {...}
}
(B)
public class Review {
...
static public Collection<Review> forProduct( Product product ) {...}
}
Guardando il codice, sceglierei (A): non è statico e non ha bisogno di un parametro. Tuttavia, sento che (A) viola il principio di responsabilità singola (SRP) e il principio aperto-chiuso (OCP) mentre (B) non:
(SRP) Quando voglio cambiare il modo in cui le recensioni vengono raccolte per un prodotto, devo cambiare la classe del Prodotto. Ma dovrebbe esserci solo un motivo per cambiare la classe di prodotto. E non sono certo le recensioni. Se confeziono tutte le funzionalità che hanno qualcosa a che fare con i prodotti nel Prodotto, saranno presto distrutte.
(OCP) Devo cambiare la classe di prodotto per estenderla con questa funzione. Penso che ciò violi la parte del principio "Chiuso per il cambiamento". Prima di ottenere la richiesta del cliente per l'implementazione delle recensioni, ho considerato il Prodotto finito e lo ho "chiuso".
Cosa è più importante: seguire i principi SOLID o avere un'interfaccia più semplice?
O sto facendo qualcosa di sbagliato qui del tutto?
Risultato
Wow, grazie per tutte le tue fantastiche risposte! È difficile sceglierne uno come risposta ufficiale.
Consentitemi di riassumere gli argomenti principali dalle risposte:
- pro (A): OCP non è una legge e anche la leggibilità del codice è importante.
- pro (A): la relazione tra entità dovrebbe essere navigabile. Entrambe le classi potrebbero conoscere questa relazione.
- pro (A) + (B): esegui entrambe le operazioni e delega in (A) a (B) in modo che il Prodotto abbia meno probabilità di essere nuovamente modificato.
- pro (C): mette i metodi finder in terza classe (servizio) dove non è statico.
- contra (B): impedisce di deridere nei test.
Alcune cose aggiuntive che i miei college al lavoro hanno contribuito:
- pro (B): il nostro framework ORM può generare automaticamente il codice per (B).
- pro (A): per motivi tecnici del nostro framework ORM, in alcuni casi sarà necessario modificare l'entità "chiusa", indipendentemente da dove si trova il cercatore. Quindi non sarò sempre in grado di attenermi a SOLID, comunque.
- contra (C): troppe storie ;-)
Conclusione
Sto usando entrambi (A) + (B) con delega per il mio progetto attuale. In un ambiente orientato ai servizi, tuttavia, andrò con (C).
Assert(5 = Math.Abs(-5));
Abs()
non è il problema, testare qualcosa che dipende da esso è. Non hai una cucitura per isolare il Code-Under-Test (CUT) dipendente per usare un finto. Ciò significa che non è possibile testarlo come unità atomica e tutti i test diventano test di integrazione che testano la logica dell'unità. Un fallimento in un test potrebbe essere in CUT o in Abs()
(o nel suo codice dipendente) e rimuove i benefici della diagnosi dei test unitari.