2016-11-05 75 views
1

有一个接口IRule带有一个方法Validate()和它的几个实现此方法的派生类。类有不同的参数(类型和参数个数)。此外,还有一个名为IPaymentProcessor的核心接口,它必须验证所有现有的规则。 我目前的任务是实现像工厂或容器这样的高级抽象,它理想地使用不同的构造函数创建所有规则,然后将它们作为IEnumerable返回以迭代并应用每个规则进行卡验证。具有不同构造函数的对象的工厂

是否有可能在.NET中使用Ninject或任何其他基于反射的库来完成任务? (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验证(CreditCard卡)规则的接口,而不仅仅是验证()。 – Evk

+0

是的,它应该是非常方便的,但我已经从其他人那里得到这个代码作为一个任务,并且不能修改它。 –

+0

无论如何将名称和其他数据传递给构造函数都不行,您需要传递整个CreditCard来以一种或另一种形式进行管理。 – Evk

回答

0

它最终似乎要验证的信用卡,所以我真的不明白为什么要创建不同的规则来验证信用卡的具体性质,而不是执行该验证卡作为一般规则整个。

如果由于我不知道的原因,您需要创建规则 来独立验证名称,过期日期,编号等,您仍然有一个非常简单的方法来执行此操作;只需在构造函数中传递de card,并让每个规则验证它应该的信息;

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

相反,你的组件应该是无状态的,并通过该IRule抽象的公共API传递,可以防止有一个工厂内建立这样的组件(因为factories are a code smell)运行时数据,并可以防止像你描述的这些维修问题在你的问题。

@InBetween作出关于做IRule抽象一般一个很好的评论,因为这允许创建一个类型安全的业务规则执行恰好定义了它验证:

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

另外请注意,我改变Validate所以它不返回布尔值,而是返回(零个或多个)验证错误的集合。这样可以更清楚地传达系统停止处理您的请求的原因。

实现可能如下:

类CreditCardNameNotEmpty:IBusinessRule { 公共IEnumerable的验证(信用卡式实体){ 如果(字符串。IsNullOrWhiteSpace(entity.Name) yield return“信用卡名称不应该为空”。 } }

通过将运行时数据移出构造函数,它现在允许我们更轻松地构造包含它们自己的依赖关系的应用程序组件。例如:

class CreditCardDateIsValid:IBusinessRule { 私有只读ILogger记录器; public CreditCardDateIsValid(ILogger记录器){ this.logger; }

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

}

虽然我们可以注入的IEnumerable<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。这是因为如果一个操作不能做它承诺的事情(在这种情况下进行支付),它应该抛出异常。通过返回一个布尔值,你将返回一个错误代码,这是我们很久以前留下的做法。

相关问题