【问题标题】:Factory of objects with different constructors具有不同构造函数的对象工厂
【发布时间】:2017-03-19 06:19:03
【问题描述】:

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

是否可以使用 Ninject 或 .NET 中的任何其他基于反射的库来完成任务? (自动夹具、起订量等)

这是我想要改进的当前解决方案。

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

【问题讨论】:

  • 也许你需要的是与 bool Validate(CreditCard card) 规则的接口,而不仅仅是 Validate()。
  • 是的,应该很方便,但是我把这个代码作为任务从其他人那里得到,不能修改。
  • 无论如何将名称和其他数据传递给构造函数都行不通,您需要将整个 CreditCard 以一种或另一种形式传递给规则。
  • 是 - 用于生产代码,否 - 用于测试任务 ;)

标签: c# .net architecture ninject abstraction


【解决方案1】:

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

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

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

如果您在创建规则时不知道要验证的卡,则需要使用一种更简洁的解决方案,即在构造函数中传递Predicate&lt;CreditCard&gt;。在这种情况下,您甚至不需要一种以上的规则类型:

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

【讨论】:

  • 感谢您的提示。展示 DI、低类依赖、SOLID 原则等方面的知识是一项学术任务。
  • @DenisK。我已经用我认为最好的解决方案更新了答案;你真的不需要不同的规则类型,专门的实例就足够了。
  • 我明白了,没有办法编辑现有的Rule1、Rule2类。他们应该保持原样。虽然,我已将您的解决方案的一部分添加到我的新分离类 RulesFactory 中。现有的 IRule 实例创建者 (Func ) 注册然后工厂按需返回所有注册的创建者。解决方案稍后会在下面发布。
【解决方案2】:

您的问题的根源在于您尝试使用运行时数据(来自仅在运行时知道的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 验证(信用卡实体){ if (string.IsNullOrWhiteSpace(entity.Name) yield return "信用卡名不能为空。"; } }

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

类 CreditCardDateIsValid : IBusinessRule { 私有只读 ILogger 记录器; 公共 CreditCardDateIsValid(ILogger 记录器){ 这个.logger; }

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

}

虽然我们可以将IEnumerable&lt;IBusinessRule&lt;T&gt;&gt; 注入到需要业务规则验证的组件中,但这并不好,因为这会迫使消费者迭代返回的集合,这会导致大量代码重复。因此,相反,我们希望对消费者隐藏IBusinessRule&lt;T&gt; 抽象,并为他们提供更关注他们需求的抽象。例如:

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。这是因为如果一个操作不能做它承诺做的事情(在这种情况下是付款)它应该抛出一个异常。通过返回一个布尔值,您将返回一个错误代码,这是我们多年前遗留下来的一种做法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-03
    • 2012-12-02
    • 2011-09-30
    • 2012-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-07
    相关资源
    最近更新 更多