更多的重构,模式和接口
下面是我们之前写过的Order类的代码段,你看有什么奇怪的地方么?
public class Order { ITaxFactory _taxFactory; public Order(Customer c):this(new DateBasedTaxFactory(c, new CustomerBasedTaxFactory(c))) { } public Order(ITaxFactory taxFactory) { _taxFactory = taxFactory; } public List<OrderItem> _orderItems = new List<OrderItem>(); public decimal CalculateTotal(Customer customer) { decimal total = _orderItems.Sum(item => item.Cost * item.Quantity); total += _taxFactory.GetTaxObject().CalculateTax(customer, total); return total; } } public interface ITax { decimal CalculateTax(Customer customer, decimal total); } public class TXTax:ITax { public decimal CalculateTax(Customer customer, decimal total) { return total * 0.08m; } }
也许你注意到在CalculateTotal方法中一些事情推迟了。我们有两个单独的Customer对象的引用。一个是在构造函数中一个是作为CalculateTotal方法的参数。这样做有些尴尬,有可能Order类使用了两个不同Customer的实例从而导致一些不稳定的行为。看看TXTax类中的代码,看出一些端倪了么?CalculateTax方法需要一个Customer参数,但是从来都没有使用它。这里我要声明一下这里违反了接口隔离原则。这个原则基本上是说类不应该实现很多从接口实现但是它不需要的功能。如果他们不使用它,那么它就不应该存在,这通常适用于接口方法,但是也适用于参数。你怎么认为呢?无论如何,如果你想从逻辑上讲,Tax对象不需要Customer对象参数是完全合理的。例如我们的NoTax类。因此不是所有实现ITax接口所必须的。
因此,进一步解释一下,在第一个版本的代码中,在特殊情况下在这个方法中我们需要一个Customer参数是为了直接实现Tax逻辑(基于State),我们现在再也不需要了因为Tax对象自己就能够处理它的业务逻辑了。因此让我们多做一些更改来适应这一点。这样看起来工作量很大但是请记住,我们我们仍然是出于设计和编码学习的目的,不要去更改生产代码。
public class Order { ITaxFactory _taxFactory; public Order(Customer c):this(new DateBasedTaxFactory(c, new CustomerBasedTaxFactory(c))) { } public Order(ITaxFactory taxFactory) { _taxFactory = taxFactory; } public List<OrderItem> _orderItems = new List<OrderItem>(); public decimal CalculateTotal() { decimal total = _orderItems.Sum(item => item.Cost * item.Quantity); total += _taxFactory.GetTaxObject().CalculateTax(total); return total; } } public interface ITax { decimal CalculateTax(decimal total); } public class TXTax:ITax { public decimal CalculateTax(decimal total) { return total * 0.08m; } }
好了,完成以后,让我们做一起其他的事情。让我来添加一些代码来简单的实际演示使用Order对象。通常在电子商务中,我们有一个篮子的概念,因此我们将使用这一点,并坚持在文章中我们学到的东西,例如编码,抽象和工厂等等。
public interface IOrderFactory { Order GetOrder(int orderId); } public class OrderFactory : IOrderFactory { public Order GetOrder(int orderId) { Order order = null;//get from repository or wherever return order; } } public class Basket { Order_order; public Basket(int sessionId,IOrderFactory orderFactory) { int orderId = 3333;//Session[sessionId].OrderId; _order = orderFactory.GetOrder(orderId); } public Basket(int sessionId):this(sessionId,new OrderFactory()) { } }
好的,现在我们有了UI需要的所有的东西来获取Backet和它的Order
如果我说我们应该创建一个叫做IOrder的接口,那么应该像这样:
public interface IOrder { decimal CalculateTotal(); int NumberOfItems(); }
然后让Order来实现它。你可能会问你自己,这到底是怎么回事。我们已经有了一个具体的Order类,除了这个Order也不会有其他什么别的了,为什么我们还要为它实现一个接口。好的,让我们想象一下,我们已经完成了我们所有的类的编码,测试并将它投入生产环境。但是这时并非所有的业务需求都已经提出来了,我们还是忘记了一些东西。我们现在需要给Order中的CalculateTotal添加运费计算。也许像这样:
public decimal CalculateTotal() { decimal shippingCost = 1.3m * _orderItems.Count; decimal total = _orderItems.Sum((item) => { return item.Cost * item.Quantity; }); total = shippingCost + total + _taxFactory.GetTaxObject().CalculateTax(total); return total; }
好的,我们现在必须要进入到Order类中来更改它。但是我们不想这样做!开放/关闭原则。那该怎么做呢?我们可以设计一个被用来扩展一个类的行为的模式而并非一定要修改这个类。装饰器模式。简单的说,装饰一个实现了相同的抽象的对象,在我们的场景是是这个IOrder接口。然后在顶层他们可以相互引用并添加功能。很迷惑么?让我们看看他是怎么工作的。我不想更改Order类,但是我们应该首先添加了IOrder接口。因此,让我们假定从一开始它就是一直存在的。
public class Order:IOrder { }
现在我们需要新的功能,当我们需要一个新的功能的时候,你总是怎么做呢?当然是创建一个类。让我们创建一个处理运费的类。
public class ShippingOrder : IOrder { IOrder _order; public ShippingOrder(IOrder order) { _order = order; } public decimal CalculateTotal() { decimal shippingCost = 1.3m * _order.NumberOfItems(); return shippingCost + _order.CalculateTotal(); } public int NumberOfItems() { return _order.NumberOfItems(); } }
但是这样做的要点是什么呢?好的,你现在有你默认的Order类了,你能够把它传递给ShippingOrder对象并包装一下它。它自己处理运费的计算处理,然后再与正常的Order对象的CalculateTotal的值相加。然后,工厂将返回一个ShippingOrder对象而不是普通的原理的Order对象。而消费者将永远不会知道其中的差别。所以让我们将它更改为它本来应该放置的位置。
public interface IOrderFactory { IOrder GetOrder(int orderId); } public class OrderFactory : IOrderFactory { public IOrder GetOrder(int orderId) { Order order = null;//get from repository database or wherever return new ShippingOrder(order); } } public class Basket { IOrder _order; public Basket(int sessionId,IOrderFactory orderFactory) { int orderId = 3333;//Session[sessionId].OrderId; whatever you are using to maintain state orderFactory.GetOrder(orderId); } public Basket(int sessionId):this(sessionId,new OrderFactory()) { } }
在这里我们得到了什么么?Basket对象使用OrderFactory来获取IOrder的引用。它不知道这个变量是Order还是ShippingOrder的引用。它也不应该知道。这正是我们想要的。OrderFactory是唯一知道是怎么回事的对象。它只是很平常的从数据库获取了一个Order对象。但后来把它放在了ShippingOrder中以便用来计算运费同时保持Order对象的不变。
现在为止,所有的东西都放置到了它应该在的地方,让我们回头再看一看。高层的经历现在想要我们来的代码能够接受优惠券和打折扣。太好了,这将是一个简单的改变,因为我们已经为自己创造了便利。
public class DiscountOrder:IOrder { IOrderedEnumerable _order; public DiscountOrder(IOrder order) { _order = order; } public decimal CalculateTotal() { return _order.CalculateTotal() - 4; } public int NumberOfItems() { return _order.NumberOfItems(); } }
我们只是创建了一个DiscountOrder类,同时出了工厂类什么都不用变
public class OrderFactory : IOrderFactory { public IOrderedEnumerable GetOrder(int orderId) { Order order = null;//get from repository or wherever return new ShippingOrder(new DiscountOrder(order)); } }