Diamo un'occhiata alle opzioni, dove possiamo posizionare il codice di validazione:
- Dentro i setter nel costruttore.
- All'interno del
build()
metodo.
- All'interno dell'entità costruita: verrà invocata con
build()
metodo quando l'entità viene creata.
L'opzione 1 ci consente di rilevare i problemi in precedenza, ma possono esserci casi complicati in cui possiamo convalidare l'input solo con il contesto completo, quindi, facendo almeno parte della convalida nel build()
metodo. Pertanto, la scelta dell'opzione 1 porterà a un codice incoerente con parte della convalida eseguita in un posto e un'altra parte eseguita in un altro posto.
L'opzione 2 non è significativamente peggiore dell'opzione 1, perché, di solito, i setter nel builder vengono invocati proprio prima build()
dell'interfaccia fluida, specialmente. Pertanto, è ancora possibile rilevare un problema abbastanza presto nella maggior parte dei casi. Tuttavia, se il builder non è l'unico modo per creare un oggetto, porterà alla duplicazione del codice di convalida, poiché dovrai averlo ovunque dove crei un oggetto. La soluzione più logica in questo caso sarà quella di mettere la validazione il più vicino possibile all'oggetto creato, cioè al suo interno. E questa è l' opzione 3 .
Dal punto di vista SOLIDO, mettere la validazione nel builder viola anche SRP: la classe builder ha già la responsabilità di aggregare i dati per costruire un oggetto. La convalida sta stabilendo contratti sul proprio stato interno, è una nuova responsabilità controllare lo stato di un altro oggetto.
Quindi, dal mio punto di vista, non solo è meglio fallire in ritardo dal punto di vista del design, ma è anche meglio fallire all'interno dell'entità costruita, piuttosto che nel costruttore stesso.
UPD: questo commento mi ha ricordato un'altra possibilità, quando la convalida all'interno del builder (opzione 1 o 2) ha senso. Ha senso se il costruttore ha i suoi contratti sugli oggetti che sta creando. Ad esempio, supponiamo che abbiamo un builder che costruisce una stringa con contenuto specifico, ad esempio un elenco di intervalli di numeri 1-2,3-4,5-6
. Questo builder può avere un metodo simile addRange(int min, int max)
. La stringa risultante non sa nulla di questi numeri, né dovrebbe saperlo. Il costruttore stesso definisce il formato della stringa e i vincoli sui numeri. Pertanto, il metodo addRange(int,int)
deve convalidare i numeri di input e generare un'eccezione se max è inferiore a min.
Detto questo, la regola generale sarà quella di validare solo i contratti definiti dal costruttore stesso.