Recentemente, mi è stato affidato il compito di riscrivere un vecchio software. L'intero software stesso è ben scritto, tranne che per l'unica cosa che mi preoccupa, le classi contenenti un'enorme quantità di codice. Molte di queste non sono altro che catene di convalida davvero grandi come:
if (!isFooValid(dataObject.getFoo()))
throw new FooException(...);
if (!isBarValid(dataObject.getBar()))
throw new BarException(...);
E questo mi ha spinto a scrivere un'API che può aiutare a disaccoppiare le catene di convalida.
Per quanto riguarda la mia comprensione, la validazione è generalmente un Pattern composito , e se scompariamo e separiamo che vogliamo dal come vogliamo farlo, otteniamo:
Se foo è valido quindi fai qualcosa.
E abbiamo un'astrazione: è valida.
Quindi sono andato su disaccoppiando come otteniamo l'astrazione è valida , ho trovato un'idea di design.
Posso avere un oggetto Result
, che contiene il messaggio sulla convalida con un semplice vero / falso per verificare se ha avuto successo o meno.
public interface Result {
// StandardResult is the default implementation of Result.
// Using the default constructor gives an instance that indicates
// "SUCCESS" state of validation.
public static final Result OK = new StandardResult();
public Throwable getError();
public boolean isOk();
public String getMessage();
}
Posso avere un oggetto Validator<T>
, che ha la logica di convalida, e quindi restituisce un oggetto Result
che contiene informazioni su cosa è successo.
public interface Validator<T> {
public Result validate(T target);
}
Questo mi consente di fare cose del tipo:
Result r = new SomeStringValidator().validate("This String");
E allo stesso modo, posso fare le catene di validazione usando un pattern Chain of Responsibility .
public class ChainValidator<T> implements Validator<T> {
// This list contains all the validators that are in the chain.
private final List<Validator<T>> validators = new ArrayList<Validator<T>>();
public CompositeResult<T> validate(T target) {
CompositeResult<T> result = new CompositeResult<T>();
for (Validator<T> v : validators) {
Result validationResult = null;
try {
validationResult = v.validate(target);
} catch (Exception ex) {
// Creating it with StandardResult(Throwable) would give
// an instance that indicates "FAILED" state.
validationResult = new StandardResult(ex);
}
result.put(v, validationResult);
}
return result;
}
private final class CompositeResult<T> implements Result {
private final Map<Validator<T>, Result> delegate = new HashMap<Validator<T>, Result>();
private Throwable failCause;
public boolean isOk() {
for (Result r : delegate.values()) {
if (!r.isOk()) {
failCause = r.getError();
return false;
}
}
return true;
}
public String getMessage() {
return delegate.toString();
}
public void put(Validator<T> validator, Result resut) {
delegate.put(validator, resut);
}
public Throwable getError() {
return failCause;
}
}
}
Funziona ma mi lascia alcune domande:
-
La primissima linea guida alla progettazione dell'API dice Non restituire null per indicare l'assenza di un valore , ma qui sto restituendo un oggetto
null
nel metodoResult#getError()
. -
L'uso di generici sul mio esempio suona come codice odore?
Questo sarà basato sull'opinione
- Vale la pena di trasformarlo in un'API anziché solo in 4 classi?
Il codice sorgente potrebbe essere trovato qui .