In generale, hai l'idea giusta. Supponiamo che "(...) potrebbero trovarsi in uno stato incoerente, e tuttavia voglio lavorare con loro". è falso e tu mai-mai vuoi vedere i tuoi oggetti in uno stato incoerente. Corriamo con questa idea e vediamo dove ci porta.
Durante il caricamento dal database
Quando carichi un oggetto da un database, devi costruire un valore che sarà un'istanza del tipo desiderato da un'istanza di un certo tipo che rappresenta il risultato ottenuto dal database. Devi deserializzare l'oggetto. Secondo me ha senso convalidare che gli invarianti richiesti dalla stiva - e un buon posto per farlo è il costruttore.
class Foo {
public:
Foo(Some_type_representing_db_result const);
};
Se hai bisogno di costruire Foo
oggetti da Some_type_representing_db_result
s, scrivi un costruttore appropriato per farlo per te (illustro la risposta usando C ++, ma sono sicuro che puoi adattare gli esempi alla tua lingua di scelta). Genera un'eccezione se il costruttore non è in grado di stabilire gli invarianti richiesti: non è necessario forzare te stesso a indebolirli per contenere alcuni valori eventualmente errati. In questo modo comprometterebbe la correttezza del tuo programma.
Se devi occuparti di oggetti rotti, ti suggerisco di creare un tipo di dati diverso, con invarianti più deboli; per esempio. Foo
e WeakerFoo
. In questo modo, il compilatore ti avviserà se tenti di utilizzare WeakerFoo
come Foo
non rotta.
Durante la gestione degli errori di convalida
Approfitta del fatto che non devi costruire immediatamente l'oggetto. Convalida i campi uno per uno e quando sei sicuro che abbiano valori accettabili, combinali nel tipo desiderato utilizzando il costruttore di quel tipo.
int count_of_stuff = a_form.count_of_stuff();
std::string name_of_stuff = a_form.name_of_stuff();
// These two functions should throw descriptive exceptions that
// will be useful to you.
check_if_count_of_stuff_is_in_acceptable_range(count_of_stuff);
check_if_name_of_stuff_is_valid(name_of_stuff);
Foo correct_foo { count_of_stuff, name_of_stuff };
Questa è davvero la stessa situazione di quella del database. È solo diverso perché hai un utente a cui puoi segnalare errori per campo.
Una nota a margine
Cerca di incorporare invarianti nei tuoi tipi. Se sai di avere un tipo i cui membri devono conformarsi ad alcune specifiche, crea un tipo per loro. Perché dovresti utilizzare un int
e ricordare di convalidarlo in modo esplicito quando puoi utilizzare NumberForWhichSpecificRangeIsValid
e farlo convalidare da solo?