走向.NET架構設計—第五章—業務層模式,原則,實踐(前篇)
前言:不管是GOF的23種設計模式,還是Flower的企業架構模式,相信很多的朋友知道或者聽說過。在那些很經典的書中,對模式都做了很精辟的解釋,本篇的目的在于看看這些模式如何應用在項目中的,并且給出一些代碼的例子,小洋也希望大家能夠真正的了解這些模式的思想,而不僅僅停留在代碼結構和表面上。
本篇的議題如下: 架構模式 設計模式 設計原則
在上一章中,我們講述了有關業務層分層的一些知識,下面我們就來看看,在具體的業務層的設計中,我們可以采用哪些模式可以将業務層設計的更加的靈活!
架構模式
首先我們就來看看,如何更加有效的組織業務規則。
Specification Pattern(需求規格模式)
這個模式的使用方法就是:把業務規則放在業務類的外面,并且封裝成為一個個傳回boolean值的算法。這些一個個的業務規則的算法不僅僅便于管理和維護,并且還可以被重用,而且很友善的組織成為複雜的業務邏輯。
下面我們就來看一個以線上租DVD的公司的例子。例子很簡單,場景也很簡單:判斷一個使用者是否可以租更多的DVD。下面就是我們設計的一個基本的類圖。(大家肯定覺得一上來就看類圖有點突兀,沒有一步步的分析,其實我是想讓大家知道,所講的是個什麼東西樣子,之後大家再慢慢的了解)
下面我們就開始做這個事情:
public interface ISpecification<T>
{
bool IsSatisfiedBy(T entity);
}
public class HasReachedMaxSpecification : ISpecification<Customer>
{
public bool IsSatisfiedBy(Customer entity)
{
return entity.TotalRentNumber > 5;
}
}
上面的代碼,其實就是把一個個的業務規則抽象出來了。我們知道,在系統中,不管業務規則多麼複雜,最後在進行業務邏輯判定的時候,最後的結果還是“是否通過”。是以在這裡就進行了抽象。
因為我們的例子是以一個線上租賃DVD為例子,使用者可以來租賃DVD,其中也是有一定的規則的,例如,如果使用者已經租了3盤DVD,那麼我們就會考慮,這個使用者時候還可以繼續租DVD。至于根據什麼判斷:可能DVD公司規定一個人最多不能超過5盤,或者DVD公司認為某個使用者的信譽不好等等。
下面我們就來定義個具體的業務規則:HasReachedRentalThresholdSpecification
根據這個規則就決定一個使用者是否可以租DVD。
代碼
public class HasReachedRentalThresholdSpecification : ISpecification<CustomerAccount>
public override bool IsSatisfiedBy(CustomerAccount candidate)
{
return candidate.NumberOfRentalsThisMonth >= 5;
這個規則定義出來後,我們就在業務類中使用這個規則:
private ISpecification<Customer> specification = null;
當然,我們可以把更多的業務規則組合進來。
這個例子到這裡就完了,這個例子中隻是簡單的采用了Specifiction模式。但是實際的情況往往是沒有這個簡單的,因為一個業務邏輯往往要組合多個多個業務規則。下面我們就來進一步的看:如果采用鍊式的結構來完成複雜的業務邏輯。
Composite Pattern(組合模式)
注:這個模式不屬于架構模式,而且GOF模式的一種,這裡列出來主要是為了配合之前的Specification模式的,大家不要在這裡糾結這個問題 J
Composite模式允許把一個集合對象當做單個的對象來使用,而且我們還可以在這個所謂的”單個對象”中不斷的嵌套。采用這種模式,可以把對象的層級關系組合成為“樹形”的結構!我個人喜歡把它稱為“容器模式”。
其實這個模式在我們在平時的ASP.NET或者WinForm ,WPF中到處可見。例如一個Panel控件,可以在裡面加入另一個Panel,然後在Panel中可以加入GroupBox,然後再GroupBox中還可以加入Button等控件。這就是.NET Framework設計中采用了Compiste模式的例子。
下面來看看Compiste模式的UML結構圖:
在上面的圖中:
1. Component是一個抽象類,這個類提供了一個Add方法,這個Add可以加入其他的Component.大家想想,這樣是否就可以很容易的實作鍊式的效果。
2. Leaf就是一個繼承Component的具體類。
看到上面圖,其實大家也可以想想在ASP.NET頁面的生命周期中到處都是這種例子:例如在ASP.NET頁面的Init事件中,因為Page本身就是一個容器,這個容器裡面包含了很多的其他的控件,如Panel,Button,而且Panel裡面還是控件。那麼在Init方法就會調用自己的子容器的Init方法,然後子容器在調用自己的子容器的Init方法,這樣就層層調用,直到最後調用到某個控件的Init的方法。這樣這個頁面的初始化就完成了。和上面的UML的結構是一樣的。
下面我們還是來看一個例子吧。繼續之前的Specification模式的讨論,看看如果結合則兩種模式來組織複雜的業務邏輯。
為了使得例子有點說服力,我們把之前的業務稍微的變複雜一點點:為了判定一個使用者是否可以租DVD,我們要進行一系列的規則判定之後才能決定結果:
1. 使用者的賬号是否處于激活的狀态
2. 使用者之前是否還欠費
3. 使用者租賃DVD的數量是否達到了規定的數量
下面首先總體來看看一些類圖的結構:
不知道大家有沒有注意一點:每次我在講述一個功能的時候,總是先讓大家看看總體的類圖的設計,然後再開始一個個的講述。其實這樣做事有原因的。在之前的文章中,一直提到“設計Design”。就是說在做一個功能之前,不是一下子就砸進去編碼,而是首先把功能考慮清楚,然後從總體上考慮功能如何實作,然後寫出一些測試代碼,最後寫出一些實作代碼的骨架。上面的類圖其實就是一個骨架。
按照之前的Specification模式的例子,我們首先條件兩個類來新增的封裝業務規則:
現在我們将例子進行擴充:為了判定一個使用者是否可以租DVD,我們要進行一系列的規則判定:
q 使用者的賬号是否處于激活的狀态。
q 使用者之前是否還欠費。
q 使用者租賃DVD的數量是否達到了規定的數量。
public class CustomerAccountStillActiveSpecification : ISpecification<CustomerAccount>
return candidate.AccountActive;
上面的代碼用來判斷使用者是否處于激活狀态
public class CustomerAccountHasLateFeesSpecification : ISpecification<CustomerAccount>
return candidate.LateFees > 0;
上面的代碼就判斷使用者是否欠費
添加完了所有的業務規則之後,好戲就開始了。
我們要把這些業務規則組合起來,放在容器中,然後隻要調用父容器的一個方法,規則驗證就一層層進行下去,就像我們之前舉的ASP.NET的Init事件一樣。
首先我們來添加一個表示容器的類:
public abstract class CompositeSpecification<T> : ISpecification<T>
public abstract bool IsSatisfiedBy(T candidate);
public ISpecification<T> And(ISpecification<T> other)
return new AndSpecification<T>(this, other);
public ISpecification<T> Not()
return new NotSpecification<T>(this);
上面的代碼有些不明白的地方,沒什麼,咱們耐心的往下面走。
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);
public class NotSpecification<T> : CompositeSpecification<T>
private ISpecification<T> _innerSpecification;
public NotSpecification(ISpecification<T> innerSpecification)
_innerSpecification = innerSpecification;
return !_innerSpecification.IsSatisfiedBy(candidate);
上面基礎代碼完成了,我們就開始實作我們想要的鍊式的效果!
我們修改之前的幾個規則,和接口的定義,如下:
public class HasReachedRentalThresholdSpecification :CompositeSpecification<CustomerAccount>
…
public class CustomerAccountStillActiveSpecification :CompositeSpecification<CustomerAccount>
…
public class CustomerAccountHasLateFeesSpecification :CompositeSpecification<CustomerAccount>
…
漫長的過程終于結束了,到了核心的部分,請看業務類現在的定義:
public class Customer
private ISpecification<Customer> hasReachedRentalThreshold;
private ISpecification<Customer> customerIsActive;
private ISpecification<Customer> customerHasLateFees;
public Customer()
hasReachedRentalThreshold = new HasReachedMaxSpecification();
customerIsActive = new CustomerAvtiveSpecification();
customerHasLateFees = new CustomerHasLateFeesSpecification();
public decimal TotalRentNumber { get; set; }
public bool IsActive { get; set; }
public decimal LateFees { get; set; }
public bool CanRent()
ISpecification<Customer>canRent =customerIsActive.And(hasReachedRentalThreshold.Not()).And(customerHasLateFees.Not());
return canRent.IsSatisfiedBy(this);
大家主要看看那個 CanRent方法
下面我們就來講講這個方法。
customerAccountActive繼承自CompositeSpecification,而Add方法的定義如下:
public ISpecification<T> And(ISpecification<T> other)
return new AndSpecification<T>(this, other);
_customerAccountIsActive.And(_hasReachedRentalThreshold.Not())的結果就是使得customerAccountIsActive内部包含了平行的兩條業務規則,結構如下:
方法傳回的結果還是一個實作了ISpecification的對象,隻不過這個對象(我們稱之為“容器A”)裡面有兩個規則了。
然後這個保量兩個業務規則的對象(容器A)再次調用Add方法,如下:
_customerAccountIsActive.And(_hasReachedRentalThreshold.Not()).
And(_customerAccountHasLateFees.Not());
此時相當于把之前那個容器A作為一個單獨對象,再次調用Add方法,于是這個三個規則組合成為一個大的規則的容器:如下。
今天就到這裡,東西不多,大家多琢磨一下!
本文轉自yanyangtian51CTO部落格,原文連結:http://blog.51cto.com/yanyangtian/423279 ,如需轉載請自行聯系原作者