источник вашей проблемы здесь является то, что вы пытаетесь построить приложение компонентов (реализации бизнес-правил) с данными времени выполнения (свойства из экземпляра CreditCard
, который известен только во время выполнения), а injecting application components with runtime data is an anti-pattern.
Вместо этого ваши компоненты должны быть лицами без гражданства, а также путем передачи данных во время выполнения через общедоступный API от IRule
абстракции, вам предотвратить того, чтобы создать такой компонент внутри завода (с factories are a code smell), и предотвратить эти проблемы обслуживания, как вы описали в ваших вопросах.
@InBetween сделал очень хороший комментарий о делая IRule
абстракции родовых, потому что это позволяет создавать типобезопасные реализации бизнес-правила, которые точно определяет то, что он проверяет:
public interface IBusinessRule<TEntity>
{
IEnumerable<string> Validate(TEntity entity);
}
Также обратите внимание, что я изменил Validate
так что он не возвращает логическое, а скорее совокупность (ноль или более) ошибок проверки. Это позволяет более четко понять, почему система перестала обрабатывать ваш запрос.
Реализации может выглядеть следующим образом:
класса CreditCardNameNotEmpty: IBusinessRule { общественного IEnumerable Validate (CreditCard лицо) { если (строка.IsNullOrWhiteSpace (entity.Name) return return "Название кредитной карты не должно быть пустым."; } }
Из-за перемещения данных во время выполнения из конструктора теперь он позволяет нам легко создавать компоненты приложений, которые содержат собственные зависимости. Например:
класс CreditCardDateIsValid: IBusinessRule { private readonly ILogger logger; public CreditCardDateIsValid (ILogger logger) { this.logger; }
public IEnumerable<string> Validate(CreditCard entity) {
// etc
}
}
Хотя мы могли инжектировать IEnumerable<IBusinessRule<T>>
на компоненты, которые требуют проверки бизнес-правил, это не было бы хорошо, чтобы сделать, потому что это заставит потребителя итерацию возвращенную коллекцию, приведет к многократному дублированию кода. Поэтому вместо этого мы хотим скрыть абстракцию IBusinessRule<T>
от потребителя и представить им абстракцию, которая более сфокусирована на их потребностях. Например:
public interface IValidator<T>
{
// Throws a ValidationException in case of a validation error.
void Validate(T instance);
}
Мы можем легко реализовать это следующим образом:
public class Validator<T> : IValidator<T>
{
private readonly IEnumerable<IBusinessRule<T>> rules;
public Validator(IEnumerable<IBusinessRule<T>> rules) {
if (rules == null) throw new ArgumentNullException(nameof(rules));
this.rules = rules;
}
public void Validate(T instance) {
if (instance == null) throw new ArgumentNullException(nameof(instance));
var errorMessages = rules.Select(rule => rule.Validate(instance)).ToArray();
if (errorMessages.Any()) throw new ValidationException(errorMessages);
}
}
Это позволяет упростить процессор оплаты к следующему:
class MyPaymentProcess : IPaymentProcessor
{
private readonly IValidator<CreditCard> creditCardValidator;
public MyPaymentProcess(IValidator<CreditCard> creditCardValidator) {
this.creditCardValidator = creditCardValidator;
}
public void MakePayment(CreditCard card)
{
this.creditCardValidator.Validate(card);
// continue the payment
}
}
Обратите внимание, что метод MakePayment
прямо сейчас не возвращает bool
. Это связано с тем, что если операция не может выполнять то, что она обещает сделать (в этом случае внесение платежа), она должна выдать исключение. Возвращая логическое значение, вы возвращаете код ошибки, который является практикой, которую мы оставили много лет назад.
Возможно, вам нужен интерфейс с правилом bool Validate (CreditCard), а не только Validate(). – Evk
Да, это должно быть очень удобно, но у меня есть этот код, как и у других людей как задача, и он не может его изменить. –
В любом случае, передача имени и других данных в конструктор не будет работать хорошо, вам необходимо передать всю кредитную карту в том или ином виде. – Evk