Diamo un'occhiata alle opzioni, dove possiamo inserire il codice di convalida:
- All'interno dei setter nel builder.
- Nel metodo
build()
.
- All'interno dell'entità costruita: verrà invocato nel metodo
build()
quando viene creata l'entità.
L'opzione 1 ci consente di rilevare i problemi in precedenza, ma ci possono essere casi complicati quando possiamo convalidare l'input solo con il contesto completo, quindi, eseguendo almeno parte della convalida nel metodo build()
. Pertanto, scegliendo l'opzione 1 si otterrà un codice incoerente con una parte della convalida eseguita in un posto e un'altra parte in un'altra posizione.
L'opzione 2 non è significativamente peggiore dell'opzione 1, perché, in genere, i setter nel builder vengono richiamati subito prima del build()
, in particolare nelle interfacce fluenti. 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, questo porterà alla duplicazione del codice di convalida, perché sarà necessario averlo ovunque ovunque si crei un oggetto. La soluzione più logica in questo caso sarà quella di mettere la convalida il più vicino possibile all'oggetto creato, cioè al suo interno. E questa è l'opzione 3 .
Dal punto di vista SOLID, mettere la convalida in 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 builder stesso.
UPD: questo commento mi ha ricordato un'altra possibilità, quando la validazione all'interno del builder (opzione 1 o 2) ha senso. Ha senso se il costruttore ha i propri contratti sugli oggetti che sta creando. Ad esempio, supponiamo di avere un builder che costruisce una stringa con contenuto specifico, ad esempio l'elenco di intervalli numerici 1-2,3-4,5-6
. Questo builder può avere un metodo come addRange(int min, int max)
. La stringa risultante non sa nulla di questi numeri, né dovrebbe sapere. 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 è minore di min.
Detto questo, la regola generale sarà quella di convalidare solo i contratti definiti dal costruttore stesso.