2016-11-05 5 views
1

하나의 메소드 Validate()와이 메소드를 구현하는 여러 파생 클래스가있는 인터페이스 IRule이 있습니다. 클래스는 다른 ctors (유형 및 인수 수)가 있습니다. 또한 기존의 모든 규칙을 검증해야하는 IPaymentProcessor라는 핵심 인터페이스가 있습니다. 내 현재 작업은 factory 나 container와 같은 높은 수준의 추상화를 구현하여 이상적으로 다른 생성자로 모든 규칙을 만든 다음 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

아마도 Validate()뿐만 아니라 bool Validate (CreditCard 카드) 규칙과의 인터페이스가 필요할 수도 있습니다. – Evk

+0

예, 훨씬 편리해야하지만이 코드는 타인의 것으로서 작업으로 사용하고 수정할 수는 없습니다. –

+0

어쨌든 생성자에 이름과 다른 데이터를 전달하는 것이 좋지 않을 경우 전체 CreditCard를 하나의 양식 또는 다른 규칙으로 전달해야합니다. – Evk

답변

0

그것은 궁극적으로 신용 카드의 유효성을 검사하려면, 그래서 당신은 신용 카드의 특정 속성을 확인하고 카드 등을 검증하는 일반적인 규칙을 구현하지 서로 다른 규칙을 만드는 이유를 정말 이해가 안 보인다 전체.

모르겠다는 이유로, 이름, 만료 날짜, 번호 등을 독립적으로 검증하는 규칙을 만들어야합니다. 그래도 매우 간단한 방법이 있습니다. 생성자에서 카드를 단순히 전달하고 각 규칙에서 예상 한 정보의 유효성을 검사하게하십시오.

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

청소기 솔루션 당신은 당신이 규칙을 만들 때 생성자 Predicate<CreditCard>에 전달하고 검증 할 것 카드를 알고하지 않은 경우에 사용해야 할 것 하나. 당신이 응용 프로그램을 구축하려고한다는 사실

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, 낮은 계급 의존성, 고체 원칙 등등에 지식을 보여주는 학문적 인 일입니다. –

+0

@DenisK. 나는 최선의 해결책이라고 생각하는 것으로 대답을 업데이트했다. 당신은 실제로 다른 규칙 유형을 필요로하지 않는다. 특수한 인스턴스가 충분하다. – InBetween

+0

나는 규칙, Rule2 인 기존 클래스를 편집 할 방법이 없다는 생각을 가지고있다. 그들은 그대로 있어야합니다. 비록 당신의 솔루션의 일부분이지만 새로운 분리 된 클래스 RulesFactory에 추가했습니다. 기존 IRule 인스턴스 생성자 (Func )가 등록 된 후 요청시 등록 된 모든 creatar를 반환합니다. 해결책은 조금 뒤에 게시 될 것입니다. –

0

여기에 문제의 원인 : Rule의 구현 인

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

:이 경우에는 더 이상의 규칙 유형을 필요로하지 않을 구성 요소 (비즈니스 규칙 구현)를 런타임 데이터 (CreditCard 인스턴스의 등록 정보 (런타임에만 알 수 있음)로 변환하는 반면 injecting application components with runtime data is an anti-pattern.

대신, 구성 요소는 무, 그리고, 당신이 (factories are a code smell 때문에) 공장 내부와 같은 구성 요소를 만들 필요 방지 IRule 추상화의 공개 API를 통해 런타임 데이터를 전달하여, 당신이 설명 된대로 이러한 유지 보수 문제를 방지한다 귀하의 질문에.

또한
public interface IBusinessRule<TEntity> 
{ 
    IEnumerable<string> Validate(TEntity entity); 
} 

내가 너무 Validate 변경 참고 :이 정확히 그것이 확인을 정의하는 형태 보증 된 비즈니스 규칙 구현을 만들 수 있기 때문에

@InBetween는 IRule 추상화 제네릭을 만드는 방법에 대한 매우 좋은 설명을했다 그것은 부울을 반환하지 않고 오히려 (0 이상) 유효성 검사 오류의 모음을 반환합니다. 이렇게하면 시스템이 요청 처리를 중단 한 이유를보다 명확하게 전달할 수 있습니다. 다음과 같이

구현 보일 수 있습니다 :

클래스 CreditCardNameNotEmpty : IBusinessRule { 공공는 IEnumerable 유효성 검사 (크레딧 카드 엔티티) { 경우 (문자열입니다.IsNullOrWhiteSpace (entity.Name) yield returns "신용 카드 이름을 입력해야합니다."; } }

생성자에서 런타임 데이터를 이동함으로써 이제는 자신의 종속성을 포함하는 응용 프로그램 구성 요소를보다 쉽게 ​​만들 수 있습니다. 예 :

클래스 CreditCardDateIsValid : IBusinessRule { private readonly ILogger logger; public CreditCardDateIsValid (ILogger 로거) { this.logger; 이 반환 된 컬렉션을 반복하는 소비자를 강제 때문에 우리가 비즈니스 규칙 유효성 검사를 필요 구성 요소로의 IEnumerable<IBusinessRule<T>>, 이것은 할 좋은되지 않을 것 주입 수 있지만 }

public IEnumerable<string> Validate(CreditCard entity) { 
     // etc 
    } 

}

하는 많은 코드 중복을 일으킬 것입니다. 따라서 대신 소비자의 추상화를 숨기고 자신의 필요에보다 중점을 둔 추상화를 제시하고자합니다. 다음과 같이

public interface IValidator<T> 
{ 
    // Throws a ValidationException in case of a validation error. 
    void Validate(T instance); 
} 

우리는 쉽게 구현할 수 있습니다 : 예를 들어 지금

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 방법 :

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); 
    } 
} 

을이 다음에 지불 프로세서를 단순화 할 수있게 해준다 더 이상 bool을 반환하지 않습니다. 이것은 작업이 약속 한 작업을 수행 할 수없는 경우 (이 경우 지급) 예외가 발생해야하기 때문입니다. 부울 값을 반환하면 오류 코드가 반환됩니다. 이는 이전에 남긴 연습입니다.