Является ли хорошая ставка для крупной проверки?
Мне нужно построить процесс, который будет проверять запись по ~200 правилам проверки. Запись может быть одного из ~10 типов. Существует некоторая сегментация от правил валидации до типов записей, но существует много совпадений, которые мешают мне корректно объединить правила валидации.
Во время разработки я рассматриваю схему цепочки ответственности для всех правил валидации. Это хорошая идея или лучший шаблон дизайна?
2 ответа
Проверка часто является составным шаблоном. Когда вы разбиваете это на части, вы хотите отделить то, что вы хотите, от того, как вы хотите это сделать, вы получите:
Если foo действителен, тогда сделайте что-нибудь.
Здесь у нас есть действительная абстракция - Caveat: этот код был взят из текущего, подобных примеров, так что вы можете найти недостающие символы и тому подобное. Но это так, вы получите картину. В дополнение
Result
Объект содержит сообщения о сбое, а также простой статус (true/false). Это позволяет вам просто спросить "прошло?" против "Если это не удалось, скажите мне, почему"
QuickCollection
а также
QuickMap
Удобные классы для того, чтобы брать любой класс и быстро превращать их в те уважаемые типы, просто назначая делегату. Для этого примера это означает, что ваш составной валидатор уже является коллекцией и может быть, например, повторен.
У вас возникла вторичная проблема в вашем вопросе: "чисто связывание", как в "Типе A" -> правилах {a,b,c}"и" Типе B" -> правилах {c,e,z}"
Это легко сделать с помощью карты. Не совсем шаблон Command, но близко
Map<Type,Validator> typeValidators = new HashMap<>();
Установите валидатор для каждого типа, а затем создайте сопоставление между типами. Это действительно лучше всего сделать в качестве конфигурации бина, если вы используете Java, но определенно используете внедрение зависимостей
public interface Validator<T>{
public Result validate(T value);
public static interface Result {
public static final Result OK = new Result() {
@Override
public String getMessage() {
return "OK";
}
@Override
public String toString() {
return "OK";
}
@Override
public boolean isOk() {
return true;
}
};
public boolean isOk();
public String getMessage();
}
}
Теперь несколько простых реализаций, чтобы показать суть:
public class MinLengthValidator implements Validator<String> {
private final SimpleResult FAILED;
private Integer minLength;
public MinLengthValidator() {
this(8);
}
public MinLengthValidator(Integer minLength) {
this.minLength = minLength;
FAILED = new SimpleResult("Password must be at least "+minLength+" characters",false);
}
@Override
public Result validate(String newPassword) {
return newPassword.length() >= minLength ? Result.OK : FAILED;
}
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
Вот еще один, с которым мы совместим
public class NotCurrentValidator implements Validator<String> {
@Autowired
@Qualifier("userPasswordEncoder")
private PasswordEncoder encoder;
private static final SimpleResult FAILED = new SimpleResult("Password cannot be your current password",false);
@Override
public Result validate(String newPassword) {
boolean passed = !encoder.matches(newPassword,user.getPassword());
return (passed ? Result.OK : FAILED);
}
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
Теперь вот составной:
public class CompositePasswordRule extends QuickCollection<Validator> implements Validator<String> {
public CompositeValidator(Collection<Validator> rules) {
super.delegate = rules;
}
public CompositeValidator(Validator<?>... rules) {
super.delegate = Arrays.asList(rules);
}
@Override
public CompositeResult validate(String newPassword) {
CompositeResult result = new CompositeResult(super.delegate.size());
for(Validator rule : super.delegate){
Result temp = rule.validate(newPassword);
if(!temp.isOk())
result.put(rule,temp);
}
return result;
}
public static class CompositeResult extends QuickMap<Validator,Result> implements Result {
private Integer appliedCount;
private CompositeResult(Integer appliedCount) {
super.delegate = VdcCollections.delimitedMap(new HashMap<PasswordRule, Result>(), "-->",", ");
this.appliedCount = appliedCount;
}
@Override
public String getMessage() {
return super.delegate.toString();
}
@Override
public String toString() {
return super.delegate.toString();
}
@Override
public boolean isOk() {
boolean isOk = true;
for (Result r : delegate.values()) {
isOk = r.isOk();
if(!isOk)
break;
}
return isOk;
}
public Integer failCount() {
return this.size();
}
public Integer passCount() {
return appliedCount - this.size();
}
}
}
и теперь фрагмент использования:
private Validator<String> pwRule = new CompositeValidator<String>(new MinLengthValidator(),new NotCurrentValidator());
Validator.Result result = pwRule.validate(newPassword);
if(!result.isOk())
throw new PasswordConstraintException("%s", result.getMessage());
user.obsoleteCurrentPassword();
user.setPassword(passwordEncoder.encode(newPassword));
user.setPwExpDate(DateTime.now().plusDays(passwordDaysToLive).toDate());
userDao.updateUser(user);
Цепочка ответственности подразумевает наличие порядка, в котором должны проводиться проверки. Я бы, вероятно, использовал что-то похожее на шаблон "Стратегия", где у вас есть набор стратегий проверки, которые применяются к конкретному типу записи. Затем вы можете использовать фабрику для проверки записи и применения правильного набора проверок.