首页 文章

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

提问于
浏览
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;
    }
  }

2 回答

  • 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

    您遇到问题的根源在于您尝试使用运行时数据(来自 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 ,因此它不返回布尔值,而是返回(零个或多个)验证错误的集合 . 这样可以更清楚地沟通系统停止处理您的请求的原因 .

    实现可能如下所示:

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

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

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

相关问题