考虑以下情况:
我想设计一个折扣计算器,它可以获得可以应用于订单的折扣。订单有两种类型:在线和店内。根据订单类型和订单总金额,折扣计算器计算折扣。
我编程在C#中演示场景,但问题与语言无关。在下面的代码中,DiscountCalculator
类通过检查输入参数的实际类型来计算折扣。
我觉得在IOrder
方法中检查GetDiscount
参数的实际类型是代码气味;因为我隐藏了接口IOrder
背后的实现细节,然后我以某种方式带出了隐藏的内容。
interface IOrder
{
int GetTotalPrice();
}
class InStoreOrder : IOrder
{
public int GetTotalPrice() { // returns the price of order }
}
class OnlineOrder : IOrder
{
public int GetTotalPrice() { // returns the price of order }
}
class DiscountCalculator
{
public int GetDiscount(IOrder order)
{
Type orderType = order.GetType();
if (orderType == typeof(OnlineOrder))
{
if (order.GetTotalPrice() < 100)
return 2;
else
return 5;
}
if (orderType == typeof(InStoreOrder))
{
if (order.GetTotalPrice() < 100)
return 3;
else
return 6;
}
else
throw new Exception("Unknown order type:" + orderType.Name);
}
}
有什么想法吗?
更新
我非常感谢人们就此进行合作。所有的解决方案不仅具有启发性,而且还带来了一种优雅的方式。
顺便说一句,自从所有答案都确定解决方案不好的时候,我在想,Abstract Factory
可能是一个不错的选择。为什么?因为我们正在处理一系列相关对象:Order
和DiscountCalculator
。
这样的事情:
Factory f = new FactoryRepo ("Online");
IOrder order = f.CreateItem();
IDiscountCalculator discounter = f.CreateDiscountCalculator();
....
通过这种方式,我认为对于未来的变化,正如@Dhruv Rai Puri指出的那样,Decorator模式可能很容易应用。
任何想法?
答案 0 :(得分:3)
是的,检查输入参数的实际类型会破坏使用接口的目的。更好的方法是像这样修改IOrder接口
interface IOrder
{
int GetTotalPrice();
int GetDiscount();
}
然后允许每个实现计算折扣但是合适。完成此操作后,您可以将DiscountCalculator中的方法简化为
order.GetDiscount();
答案 1 :(得分:2)
在我看来,这似乎是Strategy Pattern的好例子。
这是您重新制作的样本
public interface IOrder
{
int GetTotalPrice();
}
public interface IDiscountStrategy
{
int CalculateDiscount(IOrder order);
}
public class InStoreOrder : IOrder
{
public int GetTotalPrice()
{
return 25;
}
}
public class OnlineOrder : IOrder
{
public int GetTotalPrice()
{
return 25;
}
}
public class InStoreOrderDiscountStrategy : IDiscountStrategy
{
public int CalculateDiscount(IOrder order)
{
if (order.GetTotalPrice() < 100)
return 3;
else
return 6;
}
}
public class OnlineOrderDiscountStrategy : IDiscountStrategy
{
public int CalculateDiscount(IOrder order)
{
if (order.GetTotalPrice() < 100)
return 2;
else
return 5;
}
}
public class DiscountCalculator
{
readonly IDiscountStrategy _discountStrategy;
public DiscountCalculator(IDiscountStrategy strategy)
{
_discountStrategy = strategy;
}
public int GetDiscount(IOrder order)
{
int discount = _discountStrategy.CalculateDiscount(order);
return discount;
}
}
...这里是OnLineOrder的测试样本
public void OnlineOrder_Discount_Equals_2()
{
IOrder order = new OnlineOrder();
IDiscountStrategy strategy = new OnlineOrderDiscountStrategy();
DiscountCalculator calculator = new DiscountCalculator(strategy);
int discount = calculator.GetDiscount(order);
Assert.True(discount == 2);
}
我们的想法是封装特定于每种订单类型的折扣计算逻辑:在线或在店内。如果商店推出了促销活动&#34;订单类型(例如,推出新产品),模式可以扩展到包括新逻辑,同时保持&#34;打开/关闭&#34;原理
答案 2 :(得分:2)
战略的解决方案已经提出https://stackoverflow.com/a/32798708/1168342,但这个答案有一些优势。
折扣和订单是常见的域名问题。这个轮子已经重新发明了几次。我将引用Craig Larman's "Applying UML and Patterns" book第26章的解决方案:
在此解决方案中,Sale
与您的Order
类似,ISalePricingStrategy
就像您的DiscountCalculator
。
ISalePricingStrategy
是策略模式的应用程序(名称在界面中),策略始终附加到上下文对象。在这种情况下,它是Sale
(或在你的IOrder
中)。
以下是我如何看待您的问题符合Larman建议使用定价策略的UML:
如果我理解正确,您的案例都是AbsoluteDiscountOverThresholdPricingStrategy
的复合实例。让我们从您的有条件的OnlineOrders中获取代码:
if (order.GetTotalPrice() < 100)
return 2;
else
return 5;
这就像向您的订单添加两个
实例onlineOrder.addPricingStrategy(new AbsoluteDiscountOverThresholdPricingStrategy(2,0)); // orders under 100
onlineOrder.addPricingStrategy(new AbsoluteDiscountOverThresholdPricingStrategy(5,100)); // orders >= 100
因此,Larman更进了一步,并解释说您可以使用Composite模式组合这些策略。我将它应用于您的问题(提示在上面的add...
方法中):
我用粉红色的两个类是可选的。如果您始终为客户提供最佳策略(如我附加到GetTotalPrice
的注释的伪代码中),则您不需要它们。 Larman解释说,您可以更进一步说,如果适用多种策略,则计算方式是支持商店或客户。同样,它是一个实例化类并附加它的问题。执行此操作的代码可以从&#34;配置&#34;软件中的菜单命令。
使用它的代码如下所示:
IOrder onlineOrder = new OnlineOrder(); //...
...
CompositePricingStrategy comp = new CompositePricingStrategy();
comp.add(new AbsoluteDiscountOverThresholdPricingStrategy(2,0)); // orders under 100
comp.add(new AbsoluteDiscountOverThresholdPricingStrategy(5,100)); // orders >= 100
onlineOrder.setPricingStrategy(comp);
// repeat as above for instoreOrders ...
使用工厂再次采用更灵活的方式。有关Java / .NET中非常酷的示例,请参阅Larman的书。
由于这个答案类似于另一个答案,我想解释一下这种方法的一些优点,即使它更复杂。如果您在折扣逻辑中使用GetTotal()
,则它比GetDiscount()
具有一些优势:
GetTotal()
处理(封装)所有逻辑以计算总数。GetTotal()
无可挑剔地工作,而客户端代码不必做任何特殊的事情。 答案 3 :(得分:1)
是的,在定义界面后检查类型是不太好的,因为它会破坏目的。
但我对上面给出的设计解决方案,即getOrderDiscount方法不太相信。如果您在同一商店Instore中有不同的折扣或在线享受不同的折扣怎么办 - 除了特定于商品的折扣之外,还要说明临时的网站折扣。具有getOrderDiscount()方法的设计不考虑这些场景。
但如果这些情况不可行/适用,那么你可以忽略我的下一篇文章。实际上我曾在一家零售产品软件公司工作,因此我想到了很多可能性。