走向ASP.NET架构设计——第五章:业务层模式,原则,实践(中篇)
前言:设计模式并不是什么很高深的东西,至少不是那么“神乎其神”。说到底,设计模式就是一些设计思想。下面我们就走进项目,看看这些项目中这些思想是如何体现的。本系列文章会在后续文章中陆陆续续的,在恰当的时候介绍一些相应的设计模式,而不是一股脑的一起上。
设计模式
本篇文章主要是讨论的在业务层可以采用的或者常用的一些设计模式:
Factory Method
相信很多朋友对这个模式很熟悉了,平时在项目中或多或少总能看到Factory, Provider等。确实Factory Method一种创建型的模式,它的主要目的就是隐藏对象创建的细节。也就是说,客户程序(或者成为调用者)不用特定来什么创建某一种具体的类,也不依赖于特定的类,而且依赖接口或者抽象类,这样就达到了解耦,专业点的说法就是“依赖倒置”,更加直白的说法就是:客户程序可以使用很多不同的实现类,而保持代码不变。因为在需要的时候,传入一些信息,Factory Methods就返回接口或者抽象类的实现类。
很多情况下,我们一般是这样来使用Factory Method模式的:建立一个Factory类,这个类有一个静态的方法,这个方法返回一个抽象的类或者接口。然后,客户程序(或者调用程序)就传入一些信息给Factory类来,要求Factory来创建相对应,需要的具体的实现类。
下面我们就看看一个Factory Method的UML图:
在上面的图中:
1. Client类通过Factory类得到了一个IProduct接口的实现类。Client只是提供了一些信息,但是不知道具体的类是如何创建的。
2. Factory类常常基于Client类传入的信息来决定到底创建那个具体类。
3. IProduct有两个具体的实现者:ConcreteProductA, ConcreteProductB.
理论就介绍到这里了,下面我们就看看项目中如何使用的。
例子的主要基于电子商务中的送货的场景:当用户在电子商务网站上面订购了一个货物的时候,我们就要决定采用哪种方式把货物最终送到用户那里:是航空邮寄,还是轮船配送(轮船又分为很多不同的种类)等等。我们常常根据货物的价格和订购的地址来决定哪种配送方式和速递公司对我们更加的省钱。
在这个场景中,OrderService类有一个Dispatch方法,这个方法就来决定用哪种方式把货物送出。
请看下图:
下面我们就通过代码来讲述:(大家可以一起动手)
1. 创建一个名为ASPPatterns.Chaps.FactoryPattern的解决方案
2. 添加一个C#类库:ASPPatterns.Chap5.FactoryPattern.Model
下面就来建立两个业务类:Order实体,和Address值对象(关于实体和值对象,我们之前在DDD中讲过,大家可以参看之前的文章)。
Order就代表了一个订单,而Address代表的是订单被配送的地址。
public class Address
{
public string CountryCode { get; set; }
}
public class Order
{
public decimal TotalCost { get; set; }
public decimal WeightInKG { get; set; }
public string CourierTrackingId { get; set; }
public Address DispatchAddress { get; set; }
}
因为我们有很多种不同的配送获取的方式,所以我们定义一个配送者的接口,然后让不同的具体类去实现:
如下:列出IShippingCourier的代码
public interface IShippingCourier
{
string GenerateConsignmentLabelFor(Address address);
}
在IShippingCourier中有一个方法,这个方法采用Address为参数,并且返回一个配送速递公司对货物的编码回来。(至于里面怎么送的,我们就不管了,我们认为,只要速递公司把我们的获取的配送编码给我们,我们的获取就肯定已经在他们公司的配送列表中了,送货的工作有他们来做)。
配送速递的公司有很多,下面我们就选择两个速递公司的实现者:(联邦快递FedEx,敦豪快递DHL)
public class DHL : IShippingCourier
{
public string GenerateConsignmentLabelFor(Address address)
{
return "DHL-XXXX-XXXX-XXXX";
}
}
public class FedEx : IShippingCourier
{
public string GenerateConsignmentLabelFor(Address address)
{
return "FedExXXXX-XXXX-XXXX";
}
}
下面我们就来创建一个Factory,这个Factory就根据货物和包裹的重量来决定到底最终采用那个配送快递公司,如下:
public static class UKShippingCourierFactory
{
public static IShippingCourier CreateShippingCourier(Order order)
{
if ((order.TotalCost > 100) || (order.WeightInKG > 5))
return new DHL();
else
return new RoyalMail();
}
}
最后,在OrderService的Dispacth(发货)方法就调用Factory创建的IShppingCourier来发送货物。
上面的代码如果引入IoC,将会更加的灵活!上面的代码对于很多的朋友来说是非常的熟悉了。这里我也不再赘述。
Decorator
Decorator属于结构型一种,采用Decorator可以通过组合的方式将新的行为加在现有的对象上,而且不破坏现有类的代码。这种效果的达到是这样做到的:通过继承一个抽象类或者接口,同时也包含同一个将要被装饰的抽象类或者接口的实例。或者说是,这个类同时是IS-A, Has-A.
还是先给大家看张图:
在上面的图中:
1. 只要实现了IProduct接口的,就说明它是一个产品:DefaultProduct和ProductDecorator
2. DefaultProduct是一个产品的实现类,而ProductDecorator就是一个来装饰产品的类,或者说通过ProductDecorator,我们为产品引入更多的特性。至于ProductDecorator为产品引入什么特性(例如,使得产品更加的美观,好用,便宜),有不同的具体的ProductDecorator子类来实现。
下面我们还是来看一个电子商务中的例子:
场景:在电子购物网站中,我们商品平时是以原价来出售的,如果是节日,那么就会打折,如果商品快断货了,或者很抢手,可能会相应的提价。
首先我们分析,拿打折来说,可能今天是元旦,打个折;同时可能商家想吐货,也同时打个折,那么这个折扣就是两个打折方案后的累计效果。我们不能总是去改改原有的Product类的逻辑,所以就通过组合的方式,把不同的打折策略组合进去,最后组合成为一个“打折后的商品”来实现我们的变化和需求。
我们建立如下的解决方案:
首先在Model中添加一个IPrice接口:
public interface IPrice
{
decimal Cost { get; set; }
}
为什么要添加这个接口,最直接的原因就是此时商品价格是个变化点,我们这里就把整个变化点抽象出来。
添加商品类的代码:
public class Product
{
public IPrice Price { get; set; }
}
我们添加一个IPrice的实现类BasePrice.这个类就代表了一个商品的默认的价格,就是在没有打折或者提价之前的价格。
public class BasePrice : IPrice
{
private Decimal _cost;
public decimal Cost
{
get { return _cost; }
set { _cost = value; }
}
}
其实打折后的价格就是相当于在默认的价格上面做了处理,加了一些点缀,或者说把默认的价格装饰一下就成为了打折后的价格,所以下面再添加一个TradeDiscountPriceDecorator,代表打折之后的价格:
public class TradeDiscountPriceDecorator : IPrice
{
private IPrice _basePrice;
public TradeDiscountPriceDecorator(IPrice price)
{
_basePrice = price;
}public decimal Cost
{
get { return _basePrice.Cost * 0.95m; }
set { _basePrice.Cost = value; }
}
}
TradeDiscountPriceDecorator这个类包含一个IPrice接口的引用,同时有继承了这个接口。
大家可以看到Cost属性,就是把传入的IPrice的Cost属性加了一些修改和调整。如果我们传入BasePrice,经过TradeDiscountPriceDecorator装饰之后,就得到了打折后的价格。然后因为Product引用的是IPrice接口引用,而我们可以把装饰后的结果又是一个IPrice的实现者,那么就在不改变Product情况下搞定了价格变化的问题。
同理,大家看看下面的代码:可以任意校正价格:
public class CurrencyPriceDecorator : IPrice
{
private IPrice _basePrice;
private decimal _exchangeRate;
public CurrencyPriceDecorator(IPrice price, decimal exchangeRate)
{
_basePrice = price;
_exchangeRate = exchangeRate;
}
public decimal Cost
{
get { return _basePrice.Cost * _exchangeRate; }
set { _basePrice.Cost = value; }
}
}
主要的代码写完了,下面我们添加一些扩张方法来辅助:
public static class ProductCollectionExtensionMethods
{
public static void ApplyCurrencyMultiplier(this IEnumerable<Product> products)
{
foreach (Product p in products)
p.Price = new CurrencyPriceDecorator(p.Price, 0.78m);
}
public static void ApplyTradeDiscount(this IEnumerable<Product> products)
{
foreach (Product p in products)
p.Price = new TradeDiscountPriceDecorator(p.Price);
}
}
代码很简单,就是把商品的价格装饰下。
为了整个例子的完整,我们写出一些数据访问代码:
public interface IProductRepository
{
IEnumerable<Product> FindAll();
}
public class ProductService
{
private IProductRepository _productRepository;
public ProductService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public IEnumerable<Product> GetAllProducts()
{
IEnumerable<Product> products = _productRepository.FindAll();
products.ApplyTradeDiscount();
products.ApplyCurrencyMultiplier();
return products;
}
}
以上就是本篇的内容,讲述的很粗略,希望见谅,还没有写完,待续!