具有不同构造函数的对象的工厂

时间:2016-11-05 14:13:27

标签: c# .net architecture ninject abstraction

有一个接口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;
    }
  }

2 个答案:

答案 0 :(得分:0)

最终你似乎想要验证一张信用卡,所以我真的不明白你为什么要创建不同的规则来验证信用卡的特定属性,而不是实现一个整体验证卡的一般规则。 / p>

如果由于我不知道的原因,您需要创建规则 它可以独立验证名称,到期日期,数量等,您还有一种非常简单的方法;只需在构造函数中传递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);
 }

答案 1 :(得分:0)

您遇到问题的根源在于您尝试使用运行时数据构建应用程序组件(业务规则实现)(来自CreditCard实例的属性,仅在运行时知道),而{{3 }}

相反,您的组件应该是无状态的,并且通过IRule抽象的公共API传递运行时数据,您可以防止必须在工厂内创建这样的组件(从injecting application components with runtime data is an anti-pattern开始),并且您如您在问题中所述,防止出现这些维护问题。

@InBetween对使IRule抽象泛型做出了非常好的评论,因为这允许创建一个类型安全的业务规则实现,它准确定义了它验证的内容:

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

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

实施可能如下所示:

class CreditCardNameNotEmpty:IBusinessRule   {       public IEnumerable Validate(CreditCard entity){           if(string.IsNullOrWhiteSpace(entity.Name)               收益率“信用卡名称不应为空。”;       }   }

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

class CreditCardDateIsValid:IBusinessRule   {       私人只读ILogger记录器;       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。这是因为如果一个操作不能做它承诺做的事情(在这种情况下付款),它应该抛出异常。通过返回一个布尔值,您将返回一个错误代码,这是我们很久以前留下的一种做法。