[Professional ASP.NET Design Pattern 读书笔记系列] 05 业务逻辑层:模式
时间:2011-01-28 来源:李志鹏
工厂模式就是一种静态的类,它具有静态的方法,静态的方法可以根据传入的参数不同,返回不同的类的实例,而返回的不同的类的实例,由于实现了同一接口,因此可以调用同一方法。这样做的好处就是,使用者不需要知道工厂的内部情况怎么样,只需要知道工厂的接口怎么样,就能通过该接口返回不同实例,调用同一方法,得到不同结果。下面以书中的Order Service 为例:
这个例子要做的事情就是:Order订单类有CourierTrackingId这个属性,Id的值因为采用RoyalMail还是DHL的来快递而有所不同,而是用RoyalMail还是DHL,则是根据Order的TotalCose和weightinKG的值的不同来定的。
这里UKShippingCourierFactory这个类就是工厂,工厂的静态方法CreateShippingCourier传入Order类的实例,返回的是实现了IShippingCourier的对象(RoyalMail或者DHL的对象)。这样OrderService的Dispatch方法,就可以根据向CreateShippingCourier方法传入Order对象,返回IShippingCourier的实例,调用IShippingCourier的GenerateConsignmentLabelFor方法(注意IShippingCourier可能是RoyalMail的对象,也可能是DHL的对象),为Order对象取得CourierTrackingId值。
2. 什么是Decorator装饰者模式?
Decorator模式就是在不改变原有类型的定义的情况下,为其拓展出新的方法。
C#里是通过Extension Method来实现Decorator模式的,Extension Method既可以拓展系统定义的类,也可以拓展自定义的类,以及数据。具体可参考:http://msdn.microsoft.com/en-us/library/bb383977.aspx
在这本书提供的例子里,这里要实现的功能是:ProductService的GetAllProducts提供的方法可以返回可以取得的所有Product的集合,但是在返回之前,需要对其价格进行转换,包括汇率Currency和TradeDiscount计算之后,才返回包含真实价格的Product数组。
书中为了实现Decorator,采用了两种关键的技术:
1. 将Product的价格类型定义为IPrice接口,这样凡是实现了IPrice接口的类型的对象都可以作为Product的成员,同时实现了IPrice的对象还可以塞进其他一些方法,这样处于不同转换时期(BasePrice=>currency,Currency=>tradeDiscount)的对象可以拥有不同的方法。
2. 对IEnumerable<Product>进行方法拓展,这样GetAllProducts就可以调用IEnumerable拓展出来的这些方方法,而这些方法内部又可以为Product的IPrice对象赋予新的类型的对象(CurrentcyPriceDecorator和TradeDiscountPriceDecorator类型)
3. 什么是模板模式?
模板模式就是在模板类里面把把子类所要实现的方法都规定好,在C#里面具体实现为:使用一个abstract类作为父类,里面存在abstract方法和实方法,在实方法里调用abstract方法,这样我们就可以调用该abstract类所指向的对象的实方法,来执行子类所override的虚方法。这里同interface具有相同的功效。
书中所举例子实现的功能是:ReturnOrder要根据FaultyReturn和NoQuibblesReturn这两种退货方式的不同,来计算AmountToRefund的不同。
这里作者实现的流程是:
1)调用ReturnService的Process方法。
2)在ReturnService的Process方法里,根据传入的ReturnOrder的ReturnAction的不同,从ReturnProcessFactory里生成不同的ReturnProcessTemplate的子类的对象
3)调用ReturnProcessTemplate的指向的对象的Process方法。
4)在这个Template的Proces方法里面,由于调用了abstract方法,他们会被子类所override掉,所以会执行不同的计算AmountToRefund的操作。
4. 什么是状态机模式
状态机模式就是同一对象,当其内部某些属性具有不同值的是时候,它的其余属性或者方法具有不同的效果。
书中所使用的例子所要达到的功能是,客户所下的订单具有New,Shipped和Canceled的状态,当处于New的时候,可以转换成Shipped或者Canceled的状态,但是却不能从这两种状态恢复为New状态。
Order具有一个IOrderStatus类型的属性叫做_orderState,它控制着整个Order的行为。IOrderStatus具有三种实现New、Canceled和Shipped,同时由于使用了接口模式,所以很容易为订单添加新的状态。Order从一个状态转向另外一个状态,只需要改变_orderStatus指向的对象;Order能不能取消或者运输,也取决于_orderState指向的对象的属性。
5. 什么是策略模式?
策略就是对象可以在运行时根据传入的参数不同,改变算法的一种方式。
这里程序的要实现的功能是,Basket具有不同的打折方式,包括NoDiscount,MemonyOff和PercentageOff,需要根据打折方式的不同,计算TotalCost的值。
程序的做法是,构造一个工厂类BasketDiscountFactory,根据传入的打折方式不同,为Basket类的_basketDiscount属性返回不同的实现了IBasketDiscountStrategy接口的对象,然后调用它们共同实现了的GetTotalCostAfterApplyingDiscountTo方法,得到最后的价格。6. 什么是规范模式?
在业务实体外实现了包裹了返回值为布尔类型的业务逻辑的模式称为规范模式。规范模式可以用在组合模式中实现业务逻辑的链接。
7.什么是组合模式?
组合模式就是实现了可以把一组对象当作一个对象来处理的设计模式。
书中使用的例子是:一个在线的DVD租赁网站,判断一个账户能否租赁DVD,具有三个条件:1. 是否租借了超过五张 2帐户是否有余额,3帐户是否激活
这里采用逆向法进行分析:
public class CustomerAccount
{
private ISpecification<CustomerAccount> _hasReachedRentalThreshold;
private ISpecification<CustomerAccount> _customerAccountIsActive;
private ISpecification<CustomerAccount> _customerAccountHasLateFees;
public CustomerAccount()
{
_hasReachedRentalThreshold = new HasReachedRentalThresholdSpecification();
_customerAccountIsActive = new CustomerAccountStillActiveSpecification();
_customerAccountHasLateFees = new CustomerAccountHasLateFeesSpecification();
}
public decimal NumberOfRentalsThisMonth { get; set; }
public bool AccountActive { get; set; }
public decimal LateFees { get; set; }
public bool CanRent()
{
ISpecification<CustomerAccount> canRent = _customerAccountIsActive.And(_hasReachedRentalThreshold.Not()).And(_customerAccountHasLateFees.Not());
return canRent.IsSatisfiedBy(this);
}
}
1). 程序最后判定能否租赁,执行的是IsSatisfiedBy(this)方法,canRent指向的实际上是一个AndSpecification类的实例,因此执行的是AndSpecificatoin的IsSatisfiedBy方法:
public class AndSpecification<T> : CompositeSpecification<T>
{
private ISpecification<T> _leftSpecification;
private ISpecification<T> _rightSpecification;
public AndSpecification(ISpecification<T> leftSpecification, ISpecification<T> rightSpecification)
{
_leftSpecification = leftSpecification;
_rightSpecification = rightSpecification;
}
public override bool IsSatisfiedBy(T candidate)
{
return _leftSpecification.IsSatisfiedBy(candidate) && _rightSpecification.IsSatisfiedBy(candidate);
}
}
2). 这里可以看出,这个IsSatisfiedBy方法实际上是分别执行了其左支和右支的IsSatisfiedBy方法。在这里,右支的IsSatisfiedBy方法,实际上是右支接口指向的CustomerAccountHasLateFees类型的对象的IsSatisfied方法;而左支的的接口指向的是另外一个AndSpecification类型的对象,该对象由_customerAccountIsActive.And(_hasReachedRentalThreshold.Not())产生,左支执行的IsSatisfiedBy()方法实际上是这个And对象的IsSatisfied犯法。这个新的And对象,其右支指向的是HasReachedRentalThresholdSpecificatoin类型的对象,左支指向的是CustomerAccountStillActiveSpecification类型的对象。
3).到此为止,我们可以发现,CustomerAccount的三个验证规则
HasReachedRentalThresholdSpecification();
CustomerAccountStillActiveSpecification();
CustomerAccountHasLateFeesSpecification();
都在CustomerAccount类型外实现,然后通过AndSpecification和NotSpecification连接了起来。这就是一个Composite的设计模式。
8. 为什么业务逻辑层要实现依赖反转(Dependency Inversion)和依赖注入(Dependency Injection)?
对于一个没有实现以上两个规则的代码如下:
public class ProductService
{
private LinqProductRepository _productRepository;
private ChristmasProductDiscount _discountStrategy;
public ProductService()
{
_productRepository = new LinqProductRepository();
_discountStrategy = new ChristmasProductDiscount();
}
public IEnumerable<Product> GetProducts()
{
IEnumerable<Product> products = _productRepository.FindAll();
foreach (Product p in products)
p.AdjustPriceWith(_discountStrategy);
return products;
}
}
可见,对于存储方法 _productRepository,不能选用其他的存储方法,对于打折策略 _discountStrategy,也不能选用其他的打折方法。
在执行依赖反转和依赖注入以后,代码如下:
public class ProductService
{
private IProductRepository _productRepository;
public ProductService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public IEnumerable<Product> GetProductsAndApplyDiscount(IProductDiscountStrategy discount)
{
IEnumerable<Product> products = _productRepository.FindAll();
foreach (Product p in products)
p.AdjustPriceWith(discount);
return products;
}
}
可见在执行依赖反转以后,存储方法的执行依靠的是IProductRepository接口,从而实现了依赖反转;而打折策略的话,依赖的是从方法传入的IProductDiscountStrategy对象,从而实现了依赖注入。
9. 为什么要对业务逻辑层进行接口分离?
所谓接口分离,就是指如果一个接口所要实现的方法或者属性,并不是所以实现这个接口的类都需要这么多的属性的时候,应该把接口根据类的需要,有一个接口两个或者多个接口,这样继承这些接口的类就不必实现它不需要的属性。
上图是实现了接口分离的一个UML。可见,Certification分级和RunningTime上映时间这两个属性只是DVD和BD才有的,TShirt并没有,所以如果不进行接口分离,那么TShirt也会具有这两个属性,这显然是不适宜的。
10. 为什么要实现Liskov替换原则?
Liskov替换言则就是,一旦子类的对象已经被基类指定,那么凡是以后设计对该基类进行的操作,都不能再去判断该基类指向的究竟是哪一个子类。
假设有这样一个场景:一个电子商务网站的支付方式有PayPal和WorldPay两种退款方式,其中PalPay退款的话一定要先根据用户的用户名和密码,去数据库里取得一个Token,然后才能根据此token和交易金额,交易ID进行支付;而对于WorldPay退款则不存在Token的说法,只需要根据用户名和密码,商品ID、交易ID和交易金额就能一次实现交易。
public class RefundService
{
public RefundResponse Refund(RefundRequest refundRequest)
{
PaymentServiceBase paymentService = PaymentServiceFactory
.GetPaymentServiceFrom(refundRequest.Payment);
RefundResponse refundResponse = new RefundResponse();
if ((paymentService as PayPalPayment) != null)
{
((PayPalPayment)paymentService).AccountName = “Scott123-PP”;
((PayPalPayment)paymentService).Password = “ABCXYZ-PP”;
}
if ((paymentService as WorldPayPayment) != null)
{
((WorldPayPayment)paymentService)
.AccountId = “Scott123-WP”;
((WorldPayPayment)paymentService)
.AccountPassword = “ABCXYZ-WP”;
((WorldPayPayment)paymentService).ProductId = “1”;
}
string merchantResponse =
paymentService.Refund(refundRequest.RefundAmount
refundRequest.PaymentTransactionId);
refundResponse.Message = merchantResponse;
if (merchantResponse.Contains(“A_Success”) ||
merchantResponse.Contains(“Auth”))
refundResponse.Success = true;
else
refundResponse.Success = false;
return refundResponse;
}
}
这里程序的意图是,为业务逻辑层制定一个RefundService类,这个类实现了一个Refund方法,只需要对Refund方法传入一个refundRequest对象,就能返回一个refundResponse对象。
这里有两处违反了Liskov Substitution原则,一是在判断AccountId的时候,需要根据PaymentServiceBase基类对象指向的对象的具体类型,才能判定AccountId,否则不行;二是在判定退款是否成功时,需要判定对象是否包含了特定子类才有的字符串。
为了解决第一个问题,我们的做法是将参数的传递挪到Factory中去,即在根据Request type返回相应的支付方法的时候,将参数的赋值完成。
为了解决第二个问题,其根源在于paymentService.Refund返回的是一个字符串。如果返回的是一个对象,那么就可以在设计该对象类的时候添加相应的判定还款是否成功的属性。