Этот паттерн слишком абстрактен для начала. В том смысле, что вы получите более полезные обобщения, если начнете с того, что именно вы хотите сделать, а затем сделаете абстракции поверх этого. Мол, в этом случае не очевидно, что проверяется? Пользовательский ввод в некоторый интерфейс? Или данные из какого-то xml? В первом случае вам потребуется не только сообщение об ошибке, но, скорее всего, также какое-либо действие, которое обновляет интерфейс в соответствии с вашей ошибкой. И тогда абстракция валидации, скорее всего, примет совсем другую форму. И откуда будет исходный код, который выполняет проверку? Необходимость реализации специальных классов для каждого правила валидации создаст потрясающую абстракцию, но излишне сложный код, когда вы действительно захотите его использовать.
Кроме того, я бы использовал динамический тип только тогда, когда совсем нет другого пути.
Итак, если вы все еще заинтересованы, приведенный ниже код будет работать с проверкой ссылочных типов. Опять же, если вам нужна система валидации, типы значений, которые вам понадобятся для валидации, обычно являются свойствами некоторого класса, но все же полезно иметь в виду, что он не универсален.
public class ValidationResult
{
public bool HasError { get; set; } = false;
public string ErrorText { get; set; } = "No error";
public void SetError(string errorMsg)
{
this.HasError = true;
ErrorText = errorMsg;
}
}
public interface IValidationRule
{
ValidationResult Validate();
}
public class ValidationRule<T> : IValidationRule
{
private Func<T, ValidationResult> validatorFunc;
private T validationSubject;
public ValidationRule(T validationSubject, Func<T, ValidationResult> validatorFunc)
{
this.validationSubject = validationSubject;
this.validatorFunc = validatorFunc;
}
public ValidationResult Validate() => validatorFunc?.Invoke(validationSubject);
}
public interface IValidator
{
void AddRule<T>(T validationSubject, Func<T, ValidationResult> validationFunc);
ValidationResult Validate();
}
public class Validator : IValidator
{
private readonly List<IValidationRule> rules = new List<IValidationRule>();
public void AddRule<T>(T validationSubject, Func<T,ValidationResult> validationFunc)
{
rules.Add(new ValidationRule<T>(validationSubject, validationFunc));
}
public ValidationResult Validate()
{
foreach (var rule in rules)
{
var result = rule.Validate();
if (result.HasError) return result;
}
return new ValidationResult();
}
}
Чтобы добавить правило проверки, вы передаете два параметра: объект, который должен быть проверен через некоторое время, и Func, который содержит логику и возвращает ValidationResult. Как это:
public class ValidatableObject
{
public int intValue;
}
private void Test()
{
var target = new ValidatableObject();
target.intValue = 1;
var validator = new Validator();
validator.AddRule(target, (x) =>
{
var validationResult = new ValidationResult();
if (x.intValue > 10) validationResult.SetError("Exceeds max value (10)");
return validationResult;
});
log(validator.Validate().ErrorText);
target.intValie = 100;
log(validator.Validate().ErrorText);
}