走向ASP.NET架構設計——第三章:分層設計,初涉架構(中篇)
1.闡明示例需求
本篇還是用之前的電子商務網站中的一個簡單的場景來講述:在頁面上需要顯示產品的列表信息。并且根據產品的類型不同,計算出相應的折扣。 在上篇中,我們已經設計項目的邏輯分層。我們再來回顧下:
可能有的朋友認為從Smart UI立刻跳到這種分層設計,似乎快了些。其實也算是一個思想的跳躍吧。下面就來看看這種分層是如何解決之前Smart UI的問題的。
2.業務層設計
記得在之前的Smart UI的例子中,程序的業務邏輯是直接寫在了ASPX頁面后面的cs代碼中的。現在,采用分層的方法,我們采用了領域模型來組織來電子商務中的業務邏輯。有關領域模型的一些東西,我們在后續的文章中會講解的。注:領域模型模式被設計用來組織復雜的業務邏輯和關系。
下面的類圖就反映了我們之前的電子商務的需求中所用到的業務模型。
Product類就代表了電子商務中的每一個產品。Price類將會包含可算折扣的業務邏輯,并且用策略模式來具體實現折扣的算法。在ASPPatterns.Chap3.Layerd.Model添加一個接口類:IDiscountStrategy:
{
decimal ApplyExtraDiscountsTo(decimal OriginalSalePrice);
}
這個接口就用來實現不同打折的策略,這是策略模式的一種應用。這個模式允許我們在運行的時候更改不同的算法實現。在本例子中,Price類將會根據不同的產品來實現不同的打折策略。在我們之前的那個Smart UI例子中,其實這個打折的算法我們已經寫了,但是沒有分離出來,導致了每次加一個打折的算法的策略,程序就需要改動,重新編譯,部署。也就是說打折的部分是個變化點,我們應該分離出來的。
注:策略模式:用一個類來封裝一個算法的實現,并且通過切換算法的實現允許在運行時修改一個對象的行為。
在電子商務中,不是每種商品都會打折的,其實我們要實現的打折策略只有一種。但是如果這樣,我們在寫代碼的時候就要if-else判斷是否是打折的商品,其實這里還是暴露了變化點的:如果國慶那天,所有的商品都打折了,那么我們就得修改代碼。其實我們可以這樣想想:不打折的情況也算是一種打折,其他的商品打折可能是7折,不打折的情況就是10折。
{
public decimal ApplyExtraDiscountsTo(decimal OriginalSalePrice)
{
decimal price = OriginalSalePrice;
price = price * 0.95M;
return price;
}
}
public class NullDiscountStrategy : IDiscountStrategy
{
public decimal ApplyExtraDiscountsTo(decimal OriginalSalePrice)
{
return OriginalSalePrice;
}
}
下面我們來看看Price類的實現。
{
private IDiscountStrategy _discountStrategy = new NullDiscountStrategy();
private decimal _rrp;
private decimal _sellingPrice;
public Price(decimal RRP, decimal SellingPrice)
{
_rrp = RRP;
_sellingPrice = SellingPrice;
}
public void SetDiscountStrategyTo(IDiscountStrategy DiscountStrategy)
{
_discountStrategy = DiscountStrategy;
}
public decimal SellingPrice
{
get { return _discountStrategy.ApplyExtraDiscountsTo(_sellingPrice); }
}
public decimal RRP
{
get { return _rrp; }
}
public decimal Discount
{
get {
if (RRP > SellingPrice)
return (RRP - SellingPrice);
else
return 0;}
}
public decimal Savings
{
get{
if (RRP > SellingPrice)
return 1 - (SellingPrice / RRP);
else
return 0;}
}
}
Price類在設計中就是用了“依賴倒置原則”,因為它沒有采用某一個具體的打折實現算法,而且依賴于接口抽象,至于之后到底會哪種的打折算法,其實是由商品的類型來決定的。 我們還是繼續的看,現在看看Product類。
{
public int Id { get; set; }
public string Name { get; set; }
public Price Price { get; set; }
}
現在所有的業務實體就已經創建了。至于對商品是否打折,其實這是由客戶代碼來決定:根據客戶代碼傳入的商品的類型不同,然后調用不同的策略,選擇了不同的打折算法計算折扣。所以我們這里來添加一個表示商品類型的枚舉:
{
Standard = 0,
Trade = 1
}
我們將會把選擇哪種打折的策略的邏輯寫在一個單獨的地方,也就是說:只要客戶代碼傳入相應的參數信息,我們就自動的創建一個合適的打折策略對象。很明顯,這里可以采用工廠方法來實現,如下:
{
public static IDiscountStrategy GetDiscountStrategyFor(CustomerType customerType)
{
switch (customerType)
{
case CustomerType.Trade:
return new TradeDiscountStrategy();
default:
return new NullDiscountStrategy();
}
}
}
在上面的邏輯分層中,我們建立了一個Repository的類庫,其實我們就是想采用Repository模式來實現”持久化無關性”-----業務類完全不用管如何保存和獲取數據。而且由Repository決定數據的來源和保存的地方,可能是數據庫,也可能就是內存,但是不管怎么,業務類是不用管這些的。所以下面用一個接口來實現靈活性:
{
IList<Product> FindAll();
}
如果現在有很多的商品,我們想知道他們的折扣價格,最簡單的方法就是遍歷他們,判斷類型,然后應用不同的打折策略。為了更加的可讀,我們可以為商品列表建立擴展方法,如下:
{
public static void Apply(this IList<Product> products, IDiscountStrategy discountStrategy)
{
foreach (Product p in products)
{
p.Price.SetDiscountStrategyTo(discountStrategy);
}
}
}
為了簡化客戶代碼的調用工作,我們提供一個類似門戶(gateway),或者是Façade的概念:把復雜的操作邏輯隱藏,留給客戶代碼一個簡單易用的API。我們這里創建一個Service類,如下:
{
private IProductRepository _productRepository;
public ProductService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public IList<Product> GetAllProductsFor(CustomerType customerType)
{
IDiscountStrategy discountStrategy = DiscountFactory.GetDiscountStrategyFor(customerType);
IList<Product> products = _productRepository.FindAll();
products.Apply(discountStrategy);
return products;
}
}
只要客戶代碼(如顯示層中的代碼)直接調用上面的方法就可以了,而且商品的折扣也根據傳入的商品類型不同來計算。
3.服務層設計
服務層就充當應用程序的入口的角色。有時候,可以被認為是façade.不僅如此,因為service分為領域邏輯的service和門戶的service。門戶的service常常為顯示層提供強類型的View Model(有時也稱為Presentation Model)。 一個View Model就是給一個專門的View來使用的。在本例中,我們將會建立Product的View Model來顯示商品的信息。一般情況下,我們不要把業務類直接暴露給顯示層,這樣很容易緊耦合,所以在中間就上一個View Model,其實View Model和業務類的結構差不多,只是View Model做了一些調整,便于最后的顯示。關于View Model詳細的,后文講述。
注:Façade模式:為內部負責的子系統提供一個簡單的接口供外部訪問。
下面我們就來看看Product的View Model是如何寫的:
{
public int ProductId { get; set; }
public string Name { get; set; }
public string RRP { get; set; }
public string SellingPrice { get; set; }
public string Discount { get; set; }
public string Savings { get; set; }
}
可以看到,其實View Model就是做了一些顯示邏輯的處理。在這里就是多加了一些字段,這些字段就是在UI的GridView中顯示用的。我們之前的Smart UI的方法中,還建立了模板列來顯示Product類中沒有的字段,其實就相當于在UI中作了一定的顯示邏輯的處理。這里我們直接顯示ViewModel.
大家應該很熟悉Web Service:在客戶端和服務使用請求/響應的消息機制進行通信的。我們這里的客戶代碼和Service也采用這種方法,因為很有可能我們在部署的時候Service的代碼和客戶代碼(顯示層)在不同機器上。
請求的消息的結構如下:
{
public CustomerType CustomerType { get; set; }
}
服務在響應請求的時候也要定義格式,而且我們可以在響應中加入更多的屬性來判斷這個請求是否成功。所以在下面的代碼中,我們加入了Message屬性,用來在請求失敗的時候顯示錯誤信息,還添加了一個Success屬性用來判斷請求的狀態:
{
public bool Success { get; set; }
public string Message { get; set; }
public IList<ProductViewModel> Products { get; set; }
}
還有一點不要忘記了:因為Product和它對應的View Model結構不同的,而Service返回的又是ViewModel的響應,那么就需要把獲取到的Product轉換為View Model的結構。可以把轉換的代碼寫在一個特定的地方(可以認為是個Mapping的過程),為了閱讀的方便,我們可以為List<Product>添加擴展方法,直接調用,如下:
{
public static IList<ProductViewModel> ConvertToProductListViewModel(this IList<Model.Product> products)
{
IList<ProductViewModel> productViewModels = new List<ProductViewModel>();
foreach(Model.Product p in products)
{
productViewModels.Add(p.ConvertToProductViewModel());
}
return productViewModels;
}
public static ProductViewModel ConvertToProductViewModel(this Model.Product product)
{
ProductViewModel productViewModel = new ProductViewModel();
productViewModel.ProductId = product.Id;
productViewModel.Name = product.Name;
productViewModel.RRP = String.Format("{0:C}", product.Price.RRP);
productViewModel.SellingPrice = String.Format("{0:C}", product.Price.SellingPrice);
if (product.Price.Discount > 0)
productViewModel.Discount = String.Format("{0:C}", product.Price.Discount);
if (product.Price.Savings < 1 && product.Price.Savings > 0)
productViewModel.Savings = product.Price.Savings.ToString("#%");
return productViewModel;
}
}
最后,我們加入一個ProductService來與業務層的Service 類進行交互,業務層的Service會返回商品列表,然后我們現在添加的這個ProductService會把列表轉為ProductViewModels。
大家可能覺得奇怪:為什么這里添加了兩個ProductService,之前在業務層加一個,現在又加一個,是否命名有問題或者功能重復?其實在上一篇已經提過:有時在業務層類添加一個service層,主要是用來組織業務流程的,常常要幾個業務類組合在一起使用,這樣主要是為了簡化客戶程序(也就是調用這個業務層的代碼)的調用,實現類似Façade的作用。
我們現在添加的ProductService就是業務層中service層的客戶程序,因為我們調用了業務層的service,往往有時候,我們不想把自己系統的業務類的結構直接暴露給外界,如顯示層,而且也希望提供更加符合顯示層所需的數據結構,那么我們就添加了這個ProductService,提供從業務類到ViewModel的轉換。而且在這個ProductSevice中,我們也可以實現一些異常處理機制,如果涉及到了分布式調用,那么我們還可以用這個ProductService類向顯示層和UI那邊隱藏分布式的信息:實現代理模式。
今天就寫在到這里,在寫的過程中發現這篇有點長了,所以分為3篇(前,中,后)發布!不明白的地方大家多琢磨一下,也可以告訴我!下篇明天發布!見諒!