(翻译)C#中的SOLID原则 – 开放/封闭原则
The SOLID Principles in C# – Open / Closed
原文链接:http://www.remondo.net/solid-principles-csharp-open-closed/
The second post in the SOLID by Example series deals with the Open / Closed Principle (OCP). This is the notion that an object should be open for extension, but closed for modification.
SOLID示例代码系列的第二篇我们来介绍开放/封闭原则(OCP)。用一句话概括该准则就是:一个对象应该对扩展开放,对修改关闭。
In the example below we have a shopping cart with a graduated discount calculation. If we buy more than five products we get a 10% discount on the total ammount. If we buy ten units or more we get a 15% discount. If we buy twenty units or more, we get a whooping 25% discount. We pick the highest percentage if more rules apply.
在下面的示例中,我们创建了一个根据商品购买数量进行折扣累加的购物车。如果购买超过5件商品,可以得到总价10%的折扣。如果购买超过10件商品,可以得到15%的折扣。如果购买超过20件商品,可以得到不可思议的25%的折扣。当多种折扣优惠生效时,我们按最高的折扣进行处理。
using System.Collections.Generic; namespace OpenClosedPrinciple { public class ShoppingCart { private List<CartItem> _items; public decimal GetDiscountPercentage() { decimal ammount = 0; // Red flag! if (_items.Count >= 5 && _items.Count < 10) { ammount = 10; } else if (_items.Count >= 10 && _items.Count < 15) { ammount = 15; } else if (_items.Count >= 15) { ammount = 25; } return ammount; } public void Add(CartItem product) { // Add an item } public void Delete(CartItem product) { // Delete an item } } }
The implementation of the ShoppingCart class violates a number of SOLID principles, including the Single Responsibility Principle. For simplicity we keep it that way and focus solely on the Open / Closed Principle.
上述购物车的实现违背了若干SOLID原则,其中包括单一责任原则。为了简单起见,我们先将这些抛在一边而将目光单独聚焦在开放/封闭原则。
The GetDiscountPercentage() enforces some discount rules. Any change to the business rules in the future forces us to change the ShoppingCart class. This violates the Open / Closed Principle. A typical red flag is a switch statement, or if-else constructs with some business logic behind it. We need to design the ShoppingCart class in such a way that it is open to extension (like adding a new discount percentage) without modification. One way to establish this is by applying the Strategy Pattern.
GetDiscountPercentage()方法进行了一些折扣规则的判断。可以预见的是,将来任何业务规则的变更都需要我们去修改ShoppingCart类中的代码,这无疑违背了开放/封闭原则。其中,夹杂着业务逻辑的switch语句,或者if-else结构就是典型的“危险信号”。我们需要对上面的购物车进行重构,以使其在不用修改的情况下满足对扩展开放(比如添加一些折扣规则)。应用策略模式是重构上述代码的一种好的方式。
We move the business logic of calculating the discount percentage out of the ShoppingCart class. We replace this by a DiscountCalculator solely responsible for enforcing the appropriate discount rules while returning the right percentage amount.
我们将关于折扣百分比计算的逻辑从ShoppingCart类中移除,代之以一个单独的DiscountCalculator类。该类进行适当的折扣计算并返回正确的折扣百分比。
using System.Collections.Generic; using System.Linq; namespace OpenClosedPrinciple { public class DiscountCalculator : IDiscountCalculator { private readonly List<IDiscountRule> _discountRules; public DiscountCalculator(List<IDiscountRule> discountRules) { _discountRules = discountRules; } #region IDiscountCalculator Members public decimal Calculate(int itemCount) { return _discountRules .First(dr => dr.Match(itemCount)) .Ammount; } #endregion } }
One discount rule would look something like this.
其中的一个折扣规则(策略)如下所示:
namespace OpenClosedPrinciple { internal class DiscountRuleTenItems : IDiscountRule { #region IDiscountRule Members public decimal Ammount { get { return 15; } } public bool Match(int itemCount) { return itemCount >= 10 && itemCount < 15; } #endregion } }
The ShoppingCart now calls the Calculate() method of the DiscountCalculator to get the right percentage. If we want to add another discount business rule to the set, we do not need to change the ShoppingCart nor the DiscountCalculator.
现在,Shopping类调用DiscountCalculator类中的Calculate()方法获取正确的折扣百分比。如果以后我们需要添加另外一个折扣逻辑,不用再修改Shopping或者DiscountCalculator类中的代码。如下所示:
using System; using System.Collections.Generic; namespace OpenClosedPrinciple { internal class Program { private static void Main() { var discountRules = new List<IDiscountRule> { new DiscountRuleNoDiscount(), new DiscountRuleFiveItems(), new DiscountRuleTenItems(), new DiscountRuleTwentyItems(), }; var shoppingCart = new ShoppingCart( new DiscountCalculator(discountRules), new List<CartItem>()); // Add 12 items to the cart for (int i = 0; i < 12; i++) { shoppingCart.Add(new CartItem()); } Console.WriteLine("{0} items gives us a {1}% discount", shoppingCart.ItemCount, shoppingCart.GetDiscountPercentage()); Console.ReadLine(); } } }