2016-11-05 3 views
1

Существует интерфейс IRule с одним методом Validate() и несколькими производными классами, которые реализуют этот метод. Классы имеют разные ctors (типы и количество аргументов). Кроме того, есть основной интерфейс с именем IPaymentProcessor, который должен проверять все существующие правила. Моя текущая задача - реализовать абстракцию высокого уровня, такую ​​как фабрика или контейнер, который идеально создает все правила с разными конструкторами, а затем возвращает их как IEnumerable для повторения и применения каждого правила для проверки карты.Фабрика объектов с различными конструкторами

Возможно ли выполнить задачу с использованием Ninject или любой другой библиотеки на основе отражения в .NET? (AutoFixture, Moq и т. Д.)

Вот текущее решение, которое я хотел улучшить.

public interface IRule 
{ 
     bool Validate(); 
} 

    class Rule1 : IRule 
    { 
     public Rule1(string name) { ... } 
     bool Validate() { ... } 
    } 

    class Rule2 : IRule 
    { 
     public Rule1(int month, int year) { ... } 
     bool Validate() { ... } 
    } 

    interface IPaymentProcessor 
    { 
    bool MakePayment(CreditCard card); 
    } 

    class MyPaymentProcess : IPaymentProcessor 
    { 
    public bool MakePayment(CreditCard card) 
    { 
     // Here is pitfall. If we need to add/remove another rule or one of 
     // ctors changed, then we have to edit this place, which isn't flexible 
     var rules = new List<IBusinessRule>() { new Rule1(card.Name), new Rule2(card.Month, card.Year) }; 
     foreach(var r in rules) if(!r.Validate()) { return false; } 
     return true; 
    } 
    } 
+0

Возможно, вам нужен интерфейс с правилом bool Validate (CreditCard), а не только Validate(). – Evk

+0

Да, это должно быть очень удобно, но у меня есть этот код, как и у других людей как задача, и он не может его изменить. –

+0

В любом случае, передача имени и других данных в конструктор не будет работать хорошо, вам необходимо передать всю кредитную карту в том или ином виде. – Evk

ответ

0

Это в конечном счете, кажется, что вы хотите проверить кредитную карту, так что я не понимаю, почему вы создаете различные правила для проверки специфических свойств кредитной карты и не осуществлять общее правило, которое проверяет карту как целое.

Если по причинам, о которых я не знаю, вам необходимо создать правила , которые независимо подтверждают имя, дату истечения срока действия, номер и т. Д., У вас все еще есть очень простой способ сделать это; просто передайте карточку в конструкторе и пусть каждое правило проверяет информацию, которую она должна использовать;

public NameRule(CreditCard card) { ... } 
public bool Validate() => !string.IsNullOrEmpty(card.Name); 

Очиститель раствора и один вы должны использовать, если вы не знаете, карты, которую можно проверяющий при создании правил, проходит в конструкторе создается Predicate<CreditCard>. В этом случае вам не нужно даже более одного типа правил:

var nameRule = new Rule<CreditCard>(c => !string.IsNullOrEmpty(c.Name)); 
var dateRule = new Rule<CreditCard>(c => c.Date > DateTime. 

Будучи реализация Rule:

public class Rule<T> 
{ 
    private readonly Predicate<T> myPredicate; 
    public Rule(Predicate<T> predicate) 
    { 
     myPredicate = predicate; 
    } 

    public bool Validate(CreditCard card) => myPredicate(card); 
} 
+0

Спасибо за советы. Это научная задача - показать знания в DI, зависимости низкого класса, принципы SOLID и т. Д. –

+0

@DenisK. Я обновил ответ с тем, что я считаю лучшим решением; вам не нужны разные типы правил, достаточно специальных экземпляров. – InBetween

+0

У меня есть идея: нет способа редактировать существующие классы, которые являются Rule1, Rule2. Они должны оставаться такими, как есть. Хотя, часть вашего решения я добавляю к моему новому отделенному классу RulesFactory. Если существующие создатели экземпляра IRule (Func ) регистрируются, а затем фабрика возвращают все зарегистрированные креаторы по требованию. Решение опубликовано ниже чуть позже. –

0

источник вашей проблемы здесь является то, что вы пытаетесь построить приложение компонентов (реализации бизнес-правил) с данными времени выполнения (свойства из экземпляра 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. Это связано с тем, что если операция не может выполнять то, что она обещает сделать (в этом случае внесение платежа), она должна выдать исключение. Возвращая логическое значение, вы возвращаете код ошибки, который является практикой, которую мы оставили много лет назад.

 Смежные вопросы

  • Нет связанных вопросов^_^