【问题标题】:Refactoring code using Strategy Pattern使用策略模式重构代码
【发布时间】:2012-07-11 05:29:00
【问题描述】:

我有一个 GiftCouponPayment 课程。它有一个可以经常改变的商业策略逻辑——GetCouponValue()。目前的逻辑是“当Coupon Number小于2000时,coupon value应该被认为是0”。在未来的商业策略中,它可能会更改为“当优惠券发行日期小于 1/1/2000 时,优惠券价值应视为零”。它可以根据公司的管理部门更改为任何此类策略。

我们如何使用 Strategy 模式重构 GiftCouponPayment 类,以便在 GetCouponValue 方法的策略时不需要更改该类?

更新:分析职责后,我觉得“GiftCoupon”将是“GiftCouponPayment”类的更好名称。

C#代码

    public int GetCouponValue()
    {
        int effectiveValue = -1;
        if (CouponNumber < 2000)
        {
            effectiveValue = 0;
        }
        else
        {
            effectiveValue = CouponValue;
        }

        return effectiveValue;
    }

阅读

  1. Strategy Pattern - multiple return types/values

【问题讨论】:

    标签: oop design-patterns domain-driven-design cqrs


    【解决方案1】:

    GiftCouponPayment 类应该将 GiftCoupon 传递给不同的策略类。所以你的策略界面(CouponValueStrategy)应该包含一个方法:

    int getCouponValue(GiftCoupon giftCoupon)

    由于实现CouponValueStrategy的每个具体策略都可以访问GiftCoupon,因此每个都可以实现基于Coupon number或Coupon date等的算法。

    【讨论】:

    • 你是说“getCouponValue”应该是策略类职责的一部分吗?只有战略确定应该是这个班级的责任,不是吗?你能详细说明一下代码吗?
    • 不确定我是否理解,但 GiftCouponPayment 应该知道选择哪种策略,并且在 1 个策略类中的 getCouponValue 方法将具有检查 couponNumber
    【解决方案2】:

    当您的业务逻辑发生变化时,您的代码也必须随之更改,这是很自然的。

    您或许可以选择将过期检测逻辑移到规范类中:

        public class CouponIsExpiredBasedOnNumber : ICouponIsExpiredSpecification
        {
    
            public bool IsExpired( Coupon c )
            {
                 if( c.CouponNumber < 2000 )
                     return true;
                 else
                      return false;
            }
        }
    
        public class CouponIsExpiredBasedOnDate : ICouponIsExpiredSpecification
        {
           public readonly DateTime expirationDate = new DateTime (2000, 1, 1);
    
           public bool IsExpired( Coupon c )
            {
                 if( c.Date < expirationDate )
                     return true;
                 else
                      return false;
            }
        }
    
    public class Coupon
    {
         public int GetCouponValue()
         {
            ICouponIsExpiredSpecification expirationRule = GetExpirationRule();
    
            if( expirationRule.IsExpired(this) ) 
               return 0;
            else
               return this.Value;
    
         }
    }
    

    你应该问自己的问题:现在有必要让它变得如此复杂吗?难道你不能让它尽可能简单以满足当前的需求,然后在过期规则确实发生变化时对其进行重构吗?

    【讨论】:

    • 谢谢。编写 GetExpirationRule() 的最佳位置是什么?
    • 这取决于;它可能在一个单独的工厂类中。
    • 你认为是“提供者”模式还是策略模式?
    【解决方案3】:

    您可以将“优惠券价值策略”注入优惠券对象本身并调用它来计算优惠券价值。在这种情况下,可以将this 传递到策略中,以便策略可以向优惠券询问其所需的属性(例如优惠券编号):

    public interface ICouponValuePolicy
    {
      int ComputeCouponValue(GiftCouponPayment couponPayment);
    }
    
    public class GiftCouponPayment
    {
      public ICouponValuePolicy CouponValuePolicy {
        get;
        set;
      }
    
      public int GetCouponValue()
      {
        return CouponValuePolicy.ComputeCouponValue(this);
      }
    }
    

    另外,您的GiftCouponPayment 似乎真正负责两件事(付款和礼券)。提取包含CouponNumberCouponValueGetCouponValue()GiftCoupon 类可能是有意义的,并从GiftCouponPayment 引用此内容。

    【讨论】:

    • 您是否建议将 GiftCoupon 设为 DDD 中的值对象?
    • 我不认为 GiftCoupon 应该是一个价值对象,因为 - 据我了解您的领域 - GiftCoupon 是可识别的,它的身份不是由其属性的值定义的。但是,付款应该是值类型。
    • @FrederikGheysels 谢谢。你的意思是“GiftCouponPayment”应该是一个值对象吗?值对象 (GiftCouponPayment) 持有非值对象 (GiftCoupon) 是否可取?
    • @Lijo:付款绝对是一个实体,因为它由唯一的 ID 跟踪。礼券可以是实体(如果优惠券编号是唯一的)或价值对象。
    • @Lijo:付款和礼券似乎仍然是两个不同的东西。礼券甚至在用于支付某些东西之前就已经存在。
    【解决方案4】:

    您希望动态的行为是优惠券计算 - 它可以依赖于任何数量的东西:优惠券日期,优惠券号码等。我认为提供者模式会更合适,注入一个服务类计算优惠券价值。

    其本质是将业务逻辑移到 GiftCouponPayment 类之外,并使用我将调用“CouponCalculator”的类来封装业务逻辑。此类使用接口。

    interface ICouponCalculator
    {
        int Calculate (GiftCouponPayment payment);
    }
    
    public class CouponCalculator : ICouponCalculator
    {
       public int Calculate (GiftCouponPayment payment)
       {
          if (payment.CouponNumber < 2000)
          {
             return 0;
          }
          else
          {
             return payment.CouponValue;
          }
       }
    }
    

    现在你有了这个接口和类,给 GiftCouponPayment 类添加一个属性,然后修改你原来的 GetCouponValue() 方法:

    public class GiftCouponPayment
    {
       public int CouponNumber;
       public int CouponValue;
    
       public ICouponCalculator Calculator { get; set; }
    
       public int GetCouponValue()
       {
          return Calculator.Calculate(this);
       }
    }
    

    当您构造 GiftCouponPayment 类时,您将分配 Calculator 属性:

    var payment = new GiftCouponPayment() { Calculator = new CouponCalculator(); }
    var val = payment.GetCouponValue(); // uses CouponCalculator class to get value
    

    如果将计算逻辑移到 GiftCouponPayment 类之外似乎需要做很多工作,那么,就是这样!但如果这是您的要求,它确实提供了几件事:

    1. 无需更改 GiftCouponPayment 类即可调整计算逻辑。

    2. 您可以创建额外的实现 ICalculator 的类,以及一个工厂模式来决定在构造 GiftCouponPayment 时将哪个类注入。这更能说明您最初对“策略”模式的渴望——因为如果逻辑变得非常复杂,这将很有用。

    【讨论】:

    • 谢谢。您的示例是“提供者模式”吗?是什么让它提供者?您认为这里有任何其他答案作为策略模式方式吗?
    • 它是一个“提供者”,因为计算逻辑与 GiftCouponPayment 类分离,GiftCouponPayment 类不“知道”优惠券是如何计算的——优惠券计算器类提供给 GiftCouponPayment 类作为计算优惠券的服务提供商。
    • 另外,这里没有讨论真正的策略模式。策略模式是根据其他标准(日期、优惠券编号等)决定使用哪个计算器的逻辑。
    • 您能否用以下内容更新答案 - 策略类将如何“决定使用哪个计算器”。这与工厂有何不同?
    • 工厂模式通常是一个静态方法,在这种情况下,它会根据其他条件(日期、付款计数等)向您返回一个实现 ICouponCalculator 的类对象。策略模式可以说是工厂方法实现中的逻辑。本质上,“策略”是决定返回哪个ICouponCalculator 类的case 语句、if/else 语句等; “策略”本身不一定是一个类。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多