設計模式總結
Creational:建立型
Abstract Factory:抽象工廠 Kit 重要
- 封裝變化:產品物件家族
- 意圖:提供一個建立一些列相關或相互依賴物件的介面,而無需指定它們具體的類
- 適用性:
- 一個系統要獨立它的產品的建立、組合和表示
- 一個系統要由多個產品系列中的一個來配置
- 要強調一個系列相關的產品物件的設計以便進行聯合使用
- 提供一個產品庫,但只想顯示它們的介面而不是實現
- 參與者職責:
- AbstractFactory:宣告一個建立抽象產品物件的操作介面。
- ConcreteFactory:實現建立具體產品物件的操作。
- AbstractProduct:為一類產品物件宣告一個介面
- ConcreteProduct:定義一個將被相應的具體工廠建立的產品物件
- Client:僅使用AbstractFactory和AbstractProduct類宣告的介面。
- 協作:
- 在執行時建立ConcreteFactory。具體工廠具有特定實現的產品,建立不同的產品物件,應使用不同的具體工廠
- AbstractFactory將產品物件的建立延遲到ConcreteFactory子類
- 優勢劣勢:
- 具體工廠一般是單例模式。
- 如果要增加一個產品需要改變抽象介面。解決方案是給建立物件增加一個引數,根據引數指定被建立物件的種類。
- 如果客戶需要和特定的子類進行互動可能使得要向下轉換,有可能導致轉換失敗
- 類圖:
- 舉例:
通過泛型將具體工廠傳入,然後呼叫:
public void Go() { // 建立Africa非洲動物鏈並執行 var africa = new AnimalWorld<Africa>(); africa.RunFoodChain(); // 建立America美國動物鏈並執行 var america = new AnimalWorld<America>(); america.RunFoodChain(); // Wait for user input // Console.ReadKey(); //結果 //Lion eats Wildebeest //Wolf eats Bison }
Builder:建造者
- 封裝變化:如何建立一個組合物件
- 意圖:將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。
- 適用性:
- 當建立複雜物件的演算法應該獨立於該物件的組成部分以及它們的裝配方式時。
- 當構造過程必須允許被構造的物件有不同的表示時。
- 參與者職責:
- Builder:為建立一個Product物件的各個部件指定抽象介面。
- ConcreteBuilder:
- 實現Builder的介面以構造和裝配該產品的各個部件。
- 定義並跟蹤它所建立的表示
- 提供一個檢索產品的介面
- Director:構造一個使用Builder介面的物件
- Product:
- 表示被構造的複雜物件,ConcreteBuilder建立該產品的內部表示並定義它的裝配過程。
- 包含定義組成部件的類,包括將這些部件裝配成最終產品的介面
- 協作:
- 客戶建立Director物件,並用它所想要的Builder物件進行配置。
- 一旦生成產品部件,導向器就會通知生成器
- 生成器處理導向器的請求,並將部件新增到該產品中
- 客戶從生成器中檢索產品
- 優勢和劣勢
- 將產品的構造程式碼和表示程式碼分離
- Builder一步步構造複雜的物件,Director封裝了產品構造邏輯,而Builder是構造部分的具體實現。
- 類圖
- 舉例:
Shop:Director
Vehicle:Product
Factory Method:工廠方法
- 封裝變化:被例項化的子類
- 意圖:定義一個用於建立物件的介面,讓子類決定例項化哪一個類,將一個類的例項化延遲到子類
- 適用性:
- 一個類不知道它所必須建立的物件的類的時候
- 一個類希望由它的子類來指定所建立的物件
- 當類將建立物件的職責委託給多個幫助的子類中的某一個
- 參與者職責:
- Product:定義工廠方法所建立的物件介面
- ConcreteProduct:實現Product介面
- Creator:宣告工廠方法,該方法返回Product型別的物件
- ConcreteCreator:重定義工廠方法以返回一個ConcreteProduct例項
- 協作
- Creator依賴於它的子類來定義工廠方法。
- 優勢和劣勢:
- 為子類提供鉤子Hook.
- 連線平行的類的層次
- 類圖:
- 例子:
Prototype:原型
- 封裝變化:被例項化的類
- 意圖:用原型例項指定建立物件的種類,並且通過拷貝這個原型來建立新的物件
- 適用性:
- 當一個系統應該獨立於它的產品建立,構成和表示時
- 當要例項化的類是在執行時指定,
- 避免建立一個與產品層次平行的工廠層次時
- 當一個類的例項只能有幾個不同狀態組合中的一種,
- 參與者職責
- Prototype:宣告克隆自身的介面
- ConcretePrototype:實現一個克隆自身的介面
- Client:讓原型克隆自身從而建立一個新的物件
- 優勢和劣勢:
- Abstract Factory儲存一個被克隆物件的原型集合,並返回物件產品
- 類圖:
Singleton:單例
-
封裝變化:一個類的唯一例項
-
意圖:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點
-
適用情況:
- 當類只能有一個例項而且客戶可以從一個總所周知的訪問點訪問它
- 唯一例項應該通過子類化可擴充套件的。
-
參與者職責:
- Singleton:
-
協作:
-
類圖:
- 現在儘量不用單例模式,交給容器管理生命週期,幫助類用單例,或則靜態
MonoState:單態模式
public class Monostate
{
private static int itsX;
public int X
{
get { return itsX; }
set { itsX = value; }
}
}
總結
抽象工廠:創造一系列物件
工廠模式:建立一種物件
建造者:建立複雜物件
原型:複製自己
單例:全域性唯一
Strutural:結構型
Adapter:介面卡 Wrapper
-
封裝變化:物件的介面
-
意圖:將一個類的介面轉換成客戶希望的另外一個介面。Adapter模式使得原本由於介面不相容而不能在一起工作的那些類可以一起工作
-
適用情況:
- 已經存在一個類,介面不符合要求
- 建立一個可以複用的類,該類可以與其他不想關的類或不可預見的類協同工作
- 已經存在的子類,不可能對每一個都進行子類化以匹配它們的介面
-
參與者
- Target:定義Client使用的與特定領域相關的介面
- Client:符合Target介面的物件協同
- Adaptee:需要適配的介面
- Adapter:介面卡
-
優勢和劣勢
- 和橋接實現方式差不多但是理念不一樣:橋接是將介面和實現部分分離,可以對它們進行相對獨立地加以改變。Adapter意味著改變一個已有物件的介面
- Decorator:模式增強了其他物件的功能而同時又不改變它的介面。
- Decorator對應用程式的透明度要比介面卡更好,Decorator支援遞迴組合,
- Proxy在不改變它的介面的條件下,為另一個物件定義一個代理。
-
類圖:
Bridge:橋接
- 封裝變化:物件的實現
- 意圖:將抽象部分與它的實現部分分離,使它們都可以獨立地變化。
- 適用情況:
- 抽象和實現解綁
- 抽象和實現通過子類的方法加以擴充
- 參與者職責:
- Abstraction:抽象類的介面,維護一個指向Implementor型別物件的指標
- RefinedAbstraction:擴充由Abstraction定義的介面。
- Implementor:定義實現類的介面,該介面不一定要與Abstraction完全一致。
- ConcreteImplementor:實現Implementor介面。
- 協作
- Abstraction將Client請求轉發給Implementor物件。
- 優勢和劣勢:
- 僅有一個Implemenetor,不需要使用該模式
- AbstractFactory模式可以建立和配置一個特定的Bridge模式。
- Adapter模式用來幫助無關類協同工作,在設計完成後才使用,而橋接模式是在系統開始的時候被使用。使得抽象介面和實現可以獨立進行。
- 類圖:
Composite:組合
- 封裝變化:一個物件的結構和組成
- 意圖:將物件組合成樹形結構以表示“部分-整體”的層次結構。Composite使得客戶對單個物件和組合物件的使用具有一致性。
- 適用情況:物件的部分與整體層次結構
- 參與則職責:
- Component:
- 為組合中的物件宣告介面
- 適當的情況下,實現所有類共有介面的預設行為
- 一個介面用於訪問和管理Component的子元件
- 在遞迴介面中定義一個介面,用於訪問父部件。
- Leaf:
- 在組合中表示葉結點物件,葉結點沒有子結點
- 在組合中定於圖元物件的行為
- Composite:
- 定義有子部件的有哪些行為
- 儲存子部件
- 在Component介面中實現與子部件有關的操作
- Client
- 通過Component介面操縱組合部件的物件
- Component:
- 協作:
- 使用者使用Component類介面與組合介面中的物件進行互動。如果接收者是葉節點,就直接處理請求,如果是Composite,就將請求傳送到子部件中。
- 優勢和劣勢:
- 關鍵就是一個抽象類,既可以代表圖元又可以代表圖元的容器。
- 部件-父部件連線用於Chain模式,
- Decorator模式與Composite模式一起使用,通常由一個公共的父類,裝飾必須支援具有Add,Remove,GetChild操作的Component介面。
- Flyweight讓你共享元件,但不能引用其父部件
- Interator可以遍歷Composite
- Visitor,將本來應該分佈在Composite和Leaf類中的操作和行為區域性化。
- 類圖
Decorator:裝飾 wrapper 重要
- 封裝變化:物件的職責,不生成子類
- 意圖:動態地給一個物件新增一些額外的職責,就擴充套件功能而言,Decorator模式比生成子類的方式更加靈活
- 適用情況:
- 不影響其他類的情況下以動態透明的方式給單個物件新增職責
- 處理那些可以撤銷的職責
- 不能採用生成子類的方法進行擴充,
- 參與者職責:
- Component:定義一個物件介面,可以給這些物件動態地新增職責。
- ConpreteComponent:定義一個具體物件
- Decorator:裝飾者
- ConcreteDecorator
- 協作:
- Decorator將請求轉發給它的Component物件。
- 優勢和劣勢:
- 裝飾僅改變物件的職責而不改變介面
- Composite:可以將裝飾視為退化的,僅由一個元件的組合。
- Strategy可以改變物件的核心。
- 類圖:
- 例子
/// <summary>
/// The 'Component' abstract class
/// </summary>
abstract class LibraryItem<T>
{
// Each T has its own NumCopies
public static int NumCopies { get; set; }
public abstract void Display();
}
/// <summary>
/// The 'ConcreteComponent' class
/// </summary>
class Book : LibraryItem<Book>
{
string author;
string title;
// Constructor
public Book(string author, string title, int numCopies)
{
this.author = author;
this.title = title;
NumCopies = numCopies;
}
public override void Display()
{
Console.WriteLine("\nBook ------ ");
Console.WriteLine(" Author: {0}", author);
Console.WriteLine(" Title: {0}", title);
Console.WriteLine(" # Copies: {0}", NumCopies);
}
}
var borrow = new Borrowable<Video>(video);
borrow.BorrowItem("Customer #1");
borrow.BorrowItem("Customer #2");
這邊是Video,傳入其實就是libraryItem<Video>,這個泛型真的是太棒了
所有的子類都用泛型實現是一個不錯的想法
通過泛型將展示項傳進入,這個是比較常見的作法,
Facade:外觀
- 封裝變化:一個子系統的介面
- 意圖:為子系統中的一組介面提供一個一致的介面,Facade模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。
- 適用情況:為一個複雜系統提供一個簡單介面時,
- 參與者職責:
- Facade:知道哪些子系統負責處理請求,將客戶的請求代理給適當的子系統物件
- Subsystem:實現子系統的功能。
- 協作:客戶端傳送請求給Fcada的方式與子系統通訊,
- 優勢和劣勢:
- 降低客戶子系統之間的耦合度
- AbstractFactory,可以與Facade一起提供一個介面,
- Mediator的目的是對同事之間的任意通訊進行抽象
- 類圖:
Flyweight:享元
- 封裝變化:物件的儲存開銷
- 意圖:運用共享技術有效地支援大量細顆粒度的物件。
- 適用情況:
- 應用程式適用了大量的物件,
- 完全由於適用大量物件造成很大的儲存開銷
- 物件的大多數狀態都是外部狀態
- 如果刪除物件的外部狀態,可以用較少的共享物件取代很多組物件
- 應用程式不依賴於物件標識,由於Flyweight物件可以被共享,
- 參與者職責:
- Flyweight:描述一個介面,通過這個介面flyweight可以接受並作用於外部狀態
- ConcreteFlyweight:實現Flyweight介面,併為內部狀態增加儲存空間,
- UnsharedConcreteFlyweight:不共享
- FlyweightFactory建立Flyweight物件
- Client維持flyweight的引用,維持多個flyweight的外部狀態
- 協作:
- flyweight執行時所需的狀態必定是內部或外部,
- 不能對ConcreteFlyweight類進行例項化,只能從FlyweightFactory物件到ConcreteFlyweight物件。
- flyweight通常和composite模式結合起來,用共享葉結點的有向無環圖實現一個邏輯上的層次結構。
- 最好用flyweight實現State和Strategy物件。
- 類圖:
namespace DesignPatterns.Structural.Flyweight.RealWorld
{
[TestFixture]
class RealWorld
{
[Test]
public void Go()
{
// Build a document with text
string document = "AAZZBBZB";
char[] chars = document.ToCharArray();
CharacterFactory factory = new CharacterFactory();
// extrinsic state
int pointSize = 10;
// For each character use a flyweight object
foreach (char c in chars)
{
pointSize++;
Character character = factory.GetCharacter(c);
character.Display(pointSize);
}
// A (pointsize 11)
// A (pointsize 12)
// Z (pointsize 13)
// Z (pointsize 14)
// B (pointsize 15)
// B (pointsize 16)
// Z (pointsize 17)
// B (pointsize 18)
}
}
/// <summary>
/// The 'FlyweightFactory' class
/// </summary>
class CharacterFactory
{
private Dictionary<char, Character> characters = new Dictionary<char, Character>();
public Character GetCharacter(char key)
{
// Uses "lazy initialization"
Character character = null;
if (characters.ContainsKey(key))
{
character = characters[key];
}
else
{
switch (key)
{
case 'A': character = new CharacterA(); break;
case 'B': character = new CharacterB(); break;
//...
case 'Z': character = new CharacterZ(); break;
}
characters.Add(key, character);
}
return character;
}
}
/// <summary>
/// The 'Flyweight' abstract class
/// </summary>
abstract class Character
{
protected char symbol;
protected int width;
protected int height;
protected int ascent;
protected int descent;
protected int pointSize;
public abstract void Display(int pointSize);
}
/// <summary>
/// A 'ConcreteFlyweight' class
/// </summary>
class CharacterA : Character
{
// Constructor
public CharacterA()
{
this.symbol = 'A';
this.height = 100;
this.width = 120;
this.ascent = 70;
this.descent = 0;
}
public override void Display(int pointSize)
{
this.pointSize = pointSize;
Console.WriteLine(this.symbol +
" (pointsize " + this.pointSize + ")");
}
}
/// <summary>
/// A 'ConcreteFlyweight' class
/// </summary>
class CharacterB : Character
{
// Constructor
public CharacterB()
{
this.symbol = 'B';
this.height = 100;
this.width = 140;
this.ascent = 72;
this.descent = 0;
}
public override void Display(int pointSize)
{
this.pointSize = pointSize;
Console.WriteLine(this.symbol +
" (pointsize " + this.pointSize + ")");
}
}
// ... C, D, E, etc.
/// <summary>
/// A 'ConcreteFlyweight' class
/// </summary>
class CharacterZ : Character
{
// Constructor
public CharacterZ()
{
this.symbol = 'Z';
this.height = 100;
this.width = 100;
this.ascent = 68;
this.descent = 0;
}
public override void Display(int pointSize)
{
this.pointSize = pointSize;
Console.WriteLine(this.symbol +
" (pointsize " + this.pointSize + ")");
}
}
}
Proxy:代理 Surrogate
- 封裝變化:如何訪問一個物件,物件的位置
- 意圖:為其他物件提供一個代理以控制這個物件的訪問
- 適用情況:
- 遠端代理Remote Proxy:為一個物件在不同的地址空間提供區域性代表,
- 虛代理Virtual Proxy:需要建立開銷很大的物件。
- 保護代理Protection Proxy:控制對原始物件的訪問。保護代理用於物件應該有不同的訪問許可權的時候。
- 參與者職責:
- Proxy:
- 儲存一個引用使得代理可以訪問實體,控制對實體的存取,並可能負責建立和刪除它。
- RemoteProxy:負責對請求及其引數進行編碼,並向不同地址空間的實體傳送已編碼的請求
- VirtualProxy:可以快取實體的附加資訊,以便延遲對它的訪問,例如ImageProxy快取影象實體的尺寸。
- ProtectionProxy:檢查呼叫者是否具有實現一個請求所必須的訪問許可權。
- Subject:介面
- RealSubject:代理實體。
- Proxy:
- 優勢和劣勢:
- Adapter為適配的物件提供一個不同的介面。
- Decorator:裝飾是為物件新增一個或多個功能,代理則控制對物件的訪問。
- 類圖
總結
介面卡:把設計好的介面換成另一種介面
橋接:介面編排/邏輯不能改變。
組合:樹形圖,集合
裝飾:動態的給類新增職責,在原有職責的基礎上,介面不變
外觀:提供統一的API入口,將眾多細顆粒度封裝成粗顆粒度
享元:共享顆粒度物件,集合
代理:控制物件的訪問
介面卡和橋接的區別
介面卡模型
我們不想,也不能修改這個介面及其實現。同時也不可能控制其演化,只要相關的物件能與系統定義的介面協同工作即可。介面卡模式經常被用在與第三方產品的功能整合上,採用該模式適應新型別的增加的方式是開發針對這個型別的介面卡,
橋接模式則不同,參與橋接的介面是穩定的,使用者可以擴充套件和修改橋接中的類,但是不能改變介面。橋接模式通過介面繼承實現或者類繼承實現功能擴充套件。
按照GOF的說法,橋接模式和介面卡模式用於設計的不同階段,橋接模式用於設計的前期,即在設計類時將類規劃為邏輯和實現兩個大類,是他們可以分別精心演化;而介面卡模式用於設計完成之後,當發現設計完成的類無法協同工作時,可以採用介面卡模式。然而很多情況下在設計初期就要考慮介面卡模式的使用,如涉及到大量第三方應用介面的情況。
聯合
一個典型的例子是工業控制中的資料採集。不同工控廠家提供的底層資料採集介面通常不同,因此在做上層軟體設計無法預知可能遇到何種介面。為此需要定義一個通用的採集介面,然後針對具體的資料採集系統開發相應的介面卡。資料儲存需要呼叫資料採集介面獲得的資料,而資料可以儲存到關係資料庫、實時資料庫或者檔案中,。資料儲存介面和資料採集結構成了橋接,
同樣的結構也經常出現在報表相關的應用中,報表本身結構和報表輸出方式完全可以分開
介面卡和外觀
兩種模式的意圖完全不同,前者使現存系統與正在設計的系統協同工作,而後者則為視訊記憶體系統提供一個更為方便的訪問介面。簡單的說,介面卡模式為事後設計,而外觀模式則必須事前設計,因為系統依賴於外觀。總之,介面卡模式沒有引入新的介面,而外觀模式則定義了一個全新的介面。
介面卡模式用於粒度較小的功能整合,如使用權威部門所固定的無法修改並替換的現有演算法模組,將來也可能升級,這時可以使用介面卡模式。
外觀模式的使用有時比較難把握,外觀介面的定義與設計人員對業務的理解程度有很大的關係。如果介面設計過於複雜,則不如直接呼叫原系統簡單;如果介面設計過於簡單,有些功能需要呼叫原有系統才能實現,同樣達不到分裝的目的。在這種情況下,首先要考慮被封裝系統的穩定程度。如果系統處於演化階段,那麼介面定義需要複雜一些,以暴露更多的介面。這時,外觀模式更像一個大粒度的介面卡。被封裝的系統發生演化時,需要新的外觀物件,而這個外觀物件起到了介面卡的作用,
組合和裝飾
代理,介面卡和外觀
代理模式(Proxy):為其他物件提供一種代理以控制對這個物件的訪問。
介面卡模式(Adapter):將一個類的介面轉換成客戶希望的另外一個介面,使得原本介面不相容而不能一起工作的那些類可以一起工作。
外觀模式(Facade):為子系統中的一組介面提供一個一致的介面,此模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。
代理模式和介面卡模式應該說很相像,但是他們的區別也很明顯,代理模式和被代理者的介面是同一個,只是使用中客戶訪問不到被代理者,所以利用代理間接的訪問,而介面卡模式,是因為介面不同,為了讓使用者使用到統一的介面,把原先的物件通過介面卡讓使用者統一的使用,大多數運用在程式碼維護的後期,或者借用第三方庫的情況下 ,而外觀模式,是大家經常無意中使用的,就是把錯綜複雜的子系統關係封裝起來,然後提供一個簡單的介面給客戶使用,就類似於一個轉介面,可以想象成一個漏斗,中間細的那一段,越細耦合度越低,外觀模式就是為了降低耦合度。
代理模式,代理者儲存一個被代理的一個物件;介面卡模式,儲存了一個被適配的物件;而外觀模式,就儲存了各個子系統物件,然後根據實際邏輯組合。
Behavioral:行為型
Chain of Responsibility:責任鏈 重要
-
封裝變化:滿足一個請求的物件
-
意圖:解除請求的傳送者和接收者之間的耦合,使得多個物件都有機會處理這個請求,將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個物件處理它。
-
適用情況
- 多個物件可以處理一個請求,哪個物件處理該請求執行時確定
- 不明確指定接收者的情況下,向多個物件中的一個提交一個請求。
- 處理一個請求的物件集合應被動態指定
-
參與者職責
- Handle:一個處理請求的介面
- ConcreteHandle:處理它所負責的請求,訪問它的後繼者,
- Client:鏈上的具體處理者物件提交請求
-
類圖
- 例子
Approver:批准者
Purchase:採購
Director:主任
VicePresident:副總統
President:總統
Successor:接班人
在Handler中實現一個事件和一個抽象函式,在建構函式中訂閱事件。這樣任何基類都會自動訂閱事件,完成鏈式處理
namespace DesignPatterns.Behavioral.ChainofResponsibility.RealWorld
{
[TestFixture]
class RealWorld
{
[Test]
public void Go()
{
// Setup Chain of Responsibility
Approver larry = new Director();
Approver sam = new VicePresident();
Approver tammy = new President();
larry.SetSuccessor(sam);
sam.SetSuccessor(tammy);
// Generate and process purchase requests
Purchase p = new Purchase(2034, 350.00, "Supplies");
larry.ProcessRequest(p);
p = new Purchase(2035, 32590.10, "Project X");
larry.ProcessRequest(p);
p = new Purchase(2036, 122100.00, "Project Y");
larry.ProcessRequest(p);
// Director approved request# 2034
// President approved request# 2035
// Request# 2036 requires an executive meeting!
}
}
/// <summary>
/// The 'Handler' abstract class
/// 批准者
/// </summary>
abstract class Approver
{
protected Approver successor;
public void SetSuccessor(Approver successor)
{
this.successor = successor;
}
public abstract void ProcessRequest(Purchase purchase);
}
/// <summary>
/// The 'ConcreteHandler' class
/// 總經理
/// </summary>
class Director : Approver
{
public override void ProcessRequest(Purchase purchase)
{
if (purchase.Amount < 10000.0)
{
Console.WriteLine("{0} approved request# {1}",
this.GetType().Name, purchase.Number);
}
else if (successor != null)
{
successor.ProcessRequest(purchase);
}
}
}
/// <summary>
/// The 'ConcreteHandler' class
/// 副總
/// </summary>
class VicePresident : Approver
{
public override void ProcessRequest(Purchase purchase)
{
if (purchase.Amount < 25000.0)
{
Console.WriteLine("{0} approved request# {1}",
this.GetType().Name, purchase.Number);
}
else if (successor != null)
{
successor.ProcessRequest(purchase);
}
}
}
/// <summary>
/// The 'ConcreteHandler' class
/// 總統
/// </summary>
class President : Approver
{
public override void ProcessRequest(Purchase purchase)
{
if (purchase.Amount < 100000.0)
{
Console.WriteLine("{0} approved request# {1}",
this.GetType().Name, purchase.Number);
}
else
{
Console.WriteLine(
"Request# {0} requires an executive meeting!",
purchase.Number);
}
}
}
/// <summary>
/// Class holding request details
/// </summary>
class Purchase
{
int number;
double amount;
string purpose;
// Constructor
public Purchase(int number, double amount, string purpose)
{
this.number = number;
this.amount = amount;
this.purpose = purpose;
}
// Gets or sets purchase number
public int Number
{
get { return number; }
set { number = value; }
}
// Gets or sets purchase amount
public double Amount
{
get { return amount; }
set { amount = value; }
}
// Gets or sets purchase purpose
public string Purpose
{
get { return purpose; }
set { purpose = value; }
}
}
責任鏈經常與Composite一起適用,一個構件的父構件可作為它的後續。
Command:命令 action transaction
-
封裝變化:合適,怎樣滿足一個請求
-
意圖:將一個請求封裝為一個物件,從而使你可用不同的請求對客戶進行引數化;對請求排隊或記錄請求日誌,以及支援可取消的操作。
-
適用條件
- 抽象出執行動作以引數化某物件。Command模式是回撥機制的一個替代品
- 在不同的時刻指定、排列和執行請求,
- 支援取消操作
- 支援修改日誌
- 用構建在原語操作上的高層操作構造一個系統。
-
參與者職責:
- Command:宣告執行操作的介面
- ConcreteCommand:將一個接收者物件綁定於一個動作,呼叫接收者相應的操作
- Invoker:要求該命令執行這個請求,集合儲存命令,排隊,請求,具體執行
- Receiver:如何實時與執行一個請求相關的操作,任何類都可以作為一個接收者。
-
協作
- client建立一個ConcreteCommand,並指定Receiver物件。
- Invoker儲存ConcreteCommand物件
- Invoker呼叫Command的Execute來提交請求。
-
優勢和劣勢
- Composite可用來實現巨集命令
- Memento用來保持某個狀態,命令用這一狀態來取消它的效果。
-
類圖
Active Object:主動物件
namespace DesignPatterns.Behavioral.ActiveObject
{
public interface Command
{
void Execute();
}
public class ActiveObjectEngine
{
ArrayList itsCommands=new ArrayList();
public void AddCommand(Command c)
{
itsCommands.Add(c);
}
public void Run()
{
while (itsCommands.Count>0)
{
Command c = (Command) itsCommands[0];
itsCommands.RemoveAt(0);
c.Execute();
}
}
}
public class SleepCommand : Command
{
private Command wakeupCommand = null;
private ActiveObjectEngine engine = null;
private long sleepTime = 0;
private DateTime startTime;
private bool started = false;
public SleepCommand(long milliseconds, ActiveObjectEngine e, Command wakeupCommand)
{
sleepTime = milliseconds;
engine = e;
this.wakeupCommand = wakeupCommand;
}
public void Execute()
{
DateTime currentTime=DateTime.Now;
if (!started)
{
started = true;
startTime = currentTime;
engine.AddCommand(this);
}
else
{
TimeSpan elapsedTime = currentTime - startTime;
if (elapsedTime.TotalMilliseconds < sleepTime)
{
engine.AddCommand(this);
}
else
{
engine.AddCommand(wakeupCommand);
}
}
}
}
[TestFixture]
public class DelayedTyper : Command
{
private long itsDelay;
private char itsChar;
private static bool stop = false;
private static ActiveObjectEngine engine=new ActiveObjectEngine();
public DelayedTyper()
{ }
private class StopCommand : Command
{
public void Execute()
{
DelayedTyper.stop = true;
}
}
public DelayedTyper(long delay, char c)
{
itsDelay =delay;
itsChar = c;
}
public void Execute()
{
Console.Write(itsChar);
if (!stop)
DelayAndRepeat();
}
public void DelayAndRepeat()
{
engine.AddCommand(new SleepCommand(itsDelay,engine,this));
}
[Test]
public void TestDelayed()
{
engine.AddCommand(new DelayedTyper(100, '1'));
engine.AddCommand(new DelayedTyper(300, '3'));
engine.AddCommand(new DelayedTyper(500, '5'));
engine.AddCommand(new DelayedTyper(700, '7'));
Command stopCommand = new StopCommand();
engine.AddCommand(new SleepCommand(20000, engine, stopCommand));
engine.Run();
//135711311513171131511311715311131151731113151131711531113117513111315117311153111317151311131517131115311173115131113175113111531171311513111731511311153171131151311713151131117531113115131711315113117153111311517311131511317115311131175131113151173111531113171513111315171311153111731151311131751131115311713115131117315113111531711311537
}
}
}
Interpreter:直譯器
-
封裝變化:一個語言的文法及解釋
-
意圖:給定一個語言,定義它的文法的一種表示,並定義一個直譯器,該直譯器使用該表示來解釋語言中的句子。
-
適用條件:當一個語言需要解釋執行,並且可將該語言中的句子標識為一個抽象的語法樹時,可使用直譯器模式,
- 文法簡單,對於複雜的文法,文法的類層次變得龐大而無法管理。
-
參與者職責
- AbstractExpression:宣告一個抽象的解釋操作
- TerminalExpression:實現與文法中的終結符相關聯的解釋操作,一個句子中的每個終結符需要該類的例項
- NonterminalExpression:對文法中的每一個規則都需要一個該例項
- Context:上下文
- Client:客戶
-
協作:
- Client構建一個句子,然後初始上下文並呼叫直譯器操作
- 每一非終結符表示式結點定義相應子表示式解釋操作,而各終結符表示式的解釋操作構成遞迴。
- 每一結點的解釋操作用上下文來儲存和訪問直譯器的狀態。
-
優勢和劣勢:
- Composite:抽象語法樹是一個組合模式的例項
- Flyweight:說明了如何在抽象語法樹中共享終結符
- Interator:直譯器可用一個迭代器遍歷該結構
- Visitor:可用來在一個類中維護抽象語法樹中個節點的行為。
-
類圖
- 例項
處理邏輯放在基類中,只是把變化的部分,對context的判別函式放在了子類中。
namespace DesignPatterns.Behavioral.Interpreter.NETOptimized
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Construct the 'parse tree'
var tree = new List<Expression>
{
new ThousandExpression(),
new HundredExpression(),
new TenExpression(),
new OneExpression()
};
// Create the context (i.e. roman value)
//建立上下文
string roman = "MCMXXVIII";
var context = new Context { Input = roman };
// Interpret
//每個解析器走一遍解析
tree.ForEach(e => e.Interpret(context));
Console.WriteLine("{0} = {1}", roman, context.Output);
//MCMXXVIII = 1928
}
}
/// <summary>
/// The 'Context' class
/// </summary>
class Context
{
public string Input { get; set; }
public int Output { get; set; }
}
/// <summary>
/// The 'AbstractExpression' abstract class
/// </summary>
abstract class Expression
{
/// <summary>
/// 解析
/// </summary>
/// <param name="context"></param>
public void Interpret(Context context)
{
if (context.Input.Length == 0)
return;
if (context.Input.StartsWith(Nine()))//如果匹配9,9*乘數
{
context.Output += (9 * Multiplier());
context.Input = context.Input.Substring(2);
}
else if (context.Input.StartsWith(Four()))
{
context.Output += (4 * Multiplier());
context.Input = context.Input.Substring(2);
}
else if (context.Input.StartsWith(Five()))
{
context.Output += (5 * Multiplier());
context.Input = context.Input.Substring(1);
}
while (context.Input.StartsWith(One()))
{
context.Output += (1 * Multiplier());
context.Input = context.Input.Substring(1);
}
}
public abstract string One();
public abstract string Four();
public abstract string Five();
public abstract string Nine();
public abstract int Multiplier();
}
/// <summary>
/// A 'TerminalExpression' class
/// <remarks>
/// Thousand checks for the Roman Numeral M
/// </remarks>
/// </summary>
class ThousandExpression : Expression
{
public override string One() { return "M"; }
public override string Four() { return " "; }
public override string Five() { return " "; }
public override string Nine() { return " "; }
public override int Multiplier() { return 1000; }
}
/// <summary>
/// A 'TerminalExpression' class
/// <remarks>
/// Hundred checks C, CD, D or CM
/// </remarks>
/// </summary>
class HundredExpression : Expression
{
public override string One() { return "C"; }
public override string Four() { return "CD"; }
public override string Five() { return "D"; }
public override string Nine() { return "CM"; }
public override int Multiplier() { return 100; }
}
/// <summary>
/// A 'TerminalExpression' class
/// <remarks>
/// Ten checks for X, XL, L and XC
/// </remarks>
/// </summary>
class TenExpression : Expression
{
public override string One() { return "X"; }
public override string Four() { return "XL"; }
public override string Five() { return "L"; }
public override string Nine() { return "XC"; }
public override int Multiplier() { return 10; }
}
/// <summary>
/// A 'TerminalExpression' class
/// <remarks>
/// One checks for I, II, III, IV, V, VI, VI, VII, VIII, IX
/// </remarks>
/// </summary>
class OneExpression : Expression
{
public override string One() { return "I"; }
public override string Four() { return "IV"; }
public override string Five() { return "V"; }
public override string Nine() { return "IX"; }
public override int Multiplier() { return 1; }
}
}
Interator:迭代 cursor
-
封裝變化:如何遍歷,訪問一個聚合的各元素
-
意圖:提供一種方法順序訪問一個聚合物件中的各個元素,而又不需要暴露該物件的內部表示
-
適用條件:
- 訪問一個聚合物件的內容而無須暴露它的內部表示
- 支援對聚合物件的多種遍歷
- 為遍歷不同的聚合結構提供一個統一的介面,支援多型迭代。
-
參與者職責:
- Iterator:迭代器定義訪問和遍歷元素的介面
- ConcreteInterator:具體迭代器實現迭代器介面,對該聚合遍歷時跟蹤當前位置
- Aggregate:聚合定義建立相應迭代器物件的介面
- ConcreteAggregate:聚合實現建立相應迭代器的介面
-
協作:
- ConcreteIterator跟蹤聚合中的當前物件,並能夠計算出待遍歷的後續物件
-
優勢和劣勢:
- Composite:迭代器常被應用到組合這樣的遞迴結構上
- Factory Method:多型迭代器靠FactoryMethod來例項化適當的迭代器子類。
- Memento:常與迭代器模式一起適用,迭代器可適用Memento來捕獲一個迭代的狀態
-
類圖
- 例子
而在Net中可以更加的簡便
/// <summary>
/// The 'ConcreteAggregate' class
/// </summary>
/// <typeparam name="T">Collection item type</typeparam>
class ItemCollection<T> : IEnumerable<T>
{
List<T> items = new List<T>();
public void Add(T t)
{
items.Add(t);
}
// The 'ConcreteIterator'
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < Count; i++)
{
yield return items[i];
}
}
public IEnumerable<T> FrontToBack
{
get { return this; }
}
public IEnumerable<T> BackToFront
{
get
{
for (int i = Count - 1; i >= 0; i--)
{
yield return items[i];
}
}
}
public IEnumerable<T> FromToStep(int from, int to, int step)
{
for (int i = from; i <= to; i = i + step)
{
yield return items[i];
}
}
// Gets number of items
public int Count
{
get { return items.Count; }
}
// System.Collections.IEnumerable member implementation
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
使用了return yield return
Mediator:中介
- 封裝變化:物件間怎樣互動,和誰互動
- 意圖:用一箇中介物件來封裝一系列的物件互動。中介者使得各物件不需要顯示地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。
- 適用條件:
- 一組物件以定義良好但複雜的方式進行通通訊,產生的相互依賴關係結構混亂且藍衣理解
- 一個物件引用其他很多物件並且直接與這些物件通訊,導致難以複用該物件。
- 想定製一個分佈在多個類中的行為,而又不想生成太多的子類。
- 參與者職責:
- Mediator:中介者定義一個介面用於與各同事(Colleague)物件通訊
- ConcreteMediator:具體中介者通過協調各同事物件實現協作行為,瞭解並維護它的各個同事。
- Colleague Class:同事類,每一個同事類都知道它的中介者物件,每一個同事物件在需要與其他同事通訊的時候與中介者通訊。
- 協作:
- 同事向一箇中介者物件傳送和接受請求,中介者在各同事間適當地轉發請求以實現協作行為。
- 優勢和劣勢:
- Facade與Mediator不同之處在於:Facade是對一個物件子系統進行抽象,從而提供了一個更為方便的介面,協議是單項的,Facade物件對這個子系統類提出請求,但反之則不行,相反,Mediator提供了Colleague物件不支援或不能支援的協作行為,而且協作是多向。
- Colleague可適用Observer模式與Mediator通訊。
- 類圖
- 例子
聊天室
每組物件都形成對Mediator的依賴
namespace DesignPatterns.Behavioral.Mediator.NETOptimized
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Create chatroom participants
var George = new Beatle { Name = "George" };
var Paul = new Beatle { Name = "Paul" };
var Ringo = new Beatle { Name = "Ringo" };
var John = new Beatle { Name = "John" };
var Yoko = new NonBeatle { Name = "Yoko" };
// Create chatroom and register participants
var chatroom = new Chatroom();
chatroom.Register(George);
chatroom.Register(Paul);
chatroom.Register(Ringo);
chatroom.Register(John);
chatroom.Register(Yoko);
// Chatting participants
Yoko.Send("John", "Hi John!");
Paul.Send("Ringo", "All you need is love");
Ringo.Send("George", "My sweet Lord");
Paul.Send("John", "Can't buy me love");
John.Send("Yoko", "My sweet love");
}
}
/// <summary>
/// The 'Mediator' interface
/// </summary>
interface IChatroom
{
void Register(Participant participant);
void Send(string from, string to, string message);
}
/// <summary>
/// The 'ConcreteMediator' class
/// </summary>
class Chatroom : IChatroom
{
Dictionary<string, Participant> participants = new Dictionary<string, Participant>();
public void Register(Participant participant)
{
if (!participants.ContainsKey(participant.Name))
{
participants.Add(participant.Name, participant);
}
participant.Chatroom = this;
}
public void Send(string from, string to, string message)
{
var participant = participants[to];
if (participant != null)
{
participant.Receive(from, message);
}
}
}
/// <summary>
/// The 'AbstractColleague' class
/// </summary>
class Participant
{
// Gets or sets participant name
public string Name { get; set; }
// Gets or sets chatroom
public Chatroom Chatroom { get; set; }
// Send a message to given participant
public void Send(string to, string message)
{
Chatroom.Send(Name, to, message);
}
// Receive message from participant
public virtual void Receive(
string from, string message)
{
Console.WriteLine("{0} to {1}: '{2}'",
from, Name, message);
}
}
/// <summary>
/// A 'ConcreteColleague' class
/// </summary>
class Beatle : Participant
{
public override void Receive(string from, string message)
{
Console.Write("To a Beatle: ");
base.Receive(from, message);
}
}
/// <summary>
/// A 'ConcreteColleague' class
/// </summary>
class NonBeatle : Participant
{
public override void Receive(string from, string message)
{
Console.Write("To a non-Beatle: ");
base.Receive(from, message);
}
}
}
Memento:備忘錄 Token
-
封裝變化:一個物件中哪些私有資訊存放在該物件之外,以及什麼時候進行儲存
-
意圖:在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態,這樣以後就可將該物件恢復到儲存的狀態。
-
適用條件:
- 必須保證一個物件在某個時刻的(部分)狀態,以後需要時它能恢復到先前的狀態
- 如果一個介面讓其他物件直接得到這些狀態,將會暴露物件的實現細節並破壞物件的封裝性
-
參與者職責:
- Memento:
- 備忘錄儲存原發器物件的內部狀態,原發器根據需要決定備忘錄儲存原發器的哪些內部狀態
- 防止原發器以外的其他物件訪問備忘錄,備忘錄實際上由兩個介面,管理者(Caretaker)只能看到備忘錄的窄介面,它只能將備忘錄傳遞給其他物件。相反,原生髮生器能夠看到一個寬介面,允許訪問返回到先前狀態所需的所有資料,理想的情況是隻允許生成備忘錄的那個原生髮生器訪問本備忘錄的內部狀態
- Originator:
- 原生髮生器建立一個備忘錄,用以記錄當前時刻它的內部狀態。
- 使用備忘錄恢復內部狀態。
- Caretaker:
- 管理者負責儲存好備忘錄。
- 不能對備忘錄的內容進行操作或檢查。
- Memento:
-
協作:
- 管理者向原發器請求一個備忘錄,保留一段時間後,將其送回原發器,也有可能不送回
- 備忘錄是被動的,只有建立備忘錄的發生器會對它的狀態進行賦值和檢索。
-
優勢和劣勢:
- Command:命令可使用備忘錄來為可撤銷的操作維護狀態
- Iterator:備忘錄可用於迭代。
-
類圖
Caretaker:看守人
originator:鼻祖
- 例子
其實可以通過序列化和反序列化建立Memento。
namespace DesignPatterns.Behavioral.Memento.NETOptimized
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Init sales prospect through object initialization
var s = new SalesProspect //Originator
{
Name = "Joel van Halen",
Phone = "(412) 256-0990",
Budget = 25000.0
};
// Store internal state
var m = new ProspectMemory();//Caretaker
m.Memento = s.SaveMemento();
// Change originator
s.Name = "Leo Welch";
s.Phone = "(310) 209-7111";
s.Budget = 1000000.0;
// Restore saved state
s.RestoreMemento(m.Memento);
}
}
/// <summary>
/// The 'Originator' class
/// 鼻祖
/// </summary>
[Serializable]
class SalesProspect
{
string name;
string phone;
double budget;
// Gets or sets name
public string Name
{
get { return name; }
set
{
name = value;
Console.WriteLine("Name: " + name);
}
}
// Gets or sets phone
public string Phone
{
get { return phone; }
set
{
phone = value;
Console.WriteLine("Phone: " + phone);
}
}
// Gets or sets budget
public double Budget
{
get { return budget; }
set
{
budget = value;
Console.WriteLine("Budget: " + budget);
}
}
// Stores (serializes) memento
public Memento SaveMemento()
{
Console.WriteLine("\nSaving state --\n");
var memento = new Memento();
return memento.Serialize(this);
}
// Restores (deserializes) memento
public void RestoreMemento(Memento memento)
{
Console.WriteLine("\nRestoring state --\n");
var s = (SalesProspect)memento.Deserialize();
this.Name = s.Name;
this.Phone = s.Phone;
this.Budget = s.Budget;
}
}
/// <summary>
/// The 'Memento' class
/// </summary>
class Memento
{
MemoryStream stream = new MemoryStream();
SoapFormatter formatter = new SoapFormatter();
public Memento Serialize(object o)
{
formatter.Serialize(stream, o);
return this;
}
public object Deserialize()
{
stream.Seek(0, SeekOrigin.Begin);
object o = formatter.Deserialize(stream);
stream.Close();
return o;
}
}
/// <summary>
/// The 'Caretaker' class
/// Prospect:展望
/// CareTaker:看守人
/// </summary>
class ProspectMemory
{
public Memento Memento { get; set; }
}
}
Observer:觀察者 dependent public-subscrible
- 封裝變化:對各物件依賴於另一個物件,而這些物件又如何保持一致
- 意圖:定義物件間的一種一對多的依賴關係,以便當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並自動重新整理。
- 適用條件:
- 一個抽象模型由兩個方面,一個方面依賴於另一個方面,將這二者封裝在獨立的物件中,以使它們可以各自獨立地改變和複用。
- 一個物件的改變需要同時改變其他物件,而不知道具體多少物件有待改變。
- 一個物件必須通知其他物件,而它又不能假定其他物件是誰,不希望其他物件是緊耦合的。
- 參與職責:
- Subject:目標
- 目標知道它的觀察者,可以有任意多個觀察則觀察同一個目標
- 提供註冊和刪除觀察者物件的介面
- Observer:觀察者
- 為那些在目標發生改變時需要獲得通知的物件定義一個更新介面。
- ConcreteSubject:具體目標
- 將有關狀態存入各ConcreteObserver物件,當狀態發生改變時,向各個觀察者發出通知
- ConcreteObserver:具體觀察則
- 維護一個指向ConcreteSubject物件的引用
- 儲存有關的狀態,狀態和目標狀態保持一致。
- 實現Observer的更新介面,以使自身狀態與目標的狀態保持一致。
- Subject:目標
- 協作:
- ConcreteSubject發生任何可能導致其觀察者與其本身狀態不一致的改變時,它將通知它的各個觀察者。
- 在得到一個具體目標的改變通知後,ConcreteObserver物件可向目標物件查詢資訊。ConcreteObserver適用這些資訊使它的狀態與目標物件的狀態一致。
- 優勢和劣勢
- Mediator:通過封裝複雜的更新語義,ChangeManager充當目標和觀察者之間的中介者
- Singleton:ChangeManager可使用Sinleton模式來保證它是唯一的並且是可全域性訪問的
- 類圖
例子
namespace DesignPatterns.Behavioral.Observer
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Create IBM stock and attach investors
var ibm = new IBM(120.00);
// Attach 'listeners', i.e. Investors
ibm.Attach(new Investor { Name = "Sorros" });
ibm.Attach(new Investor { Name = "Berkshire" });
// Fluctuating prices will notify listening investors
ibm.Price = 120.10;
ibm.Price = 121.00;
ibm.Price = 120.50;
ibm.Price = 120.75;
}
}
// Custom event arguments
public class ChangeEventArgs : EventArgs
{
// Gets or sets symbol
public string Symbol { get; set; }
// Gets or sets price
public double Price { get; set; }
}
/// <summary>
/// The 'Subject' abstract class
/// </summary>
abstract class Stock
{
protected string symbol;
protected double price;
// Constructor
public Stock(string symbol, double price)
{
this.symbol = symbol;
this.price = price;
}
// Event
public event EventHandler<ChangeEventArgs> Change;
// Invoke the Change event
public virtual void OnChange(ChangeEventArgs e)
{
if (Change != null)
{
Change(this, e);
}
}
public void Attach(IInvestor investor)
{
Change += investor.Update;
}
public void Detach(IInvestor investor)
{
Change -= investor.Update;
}
// Gets or sets the price
public double Price
{
get { return price; }
set
{
if (price != value)
{
price = value;
OnChange(new ChangeEventArgs { Symbol = symbol, Price = price });
Console.WriteLine("");
}
}
}
}
/// <summary>
/// The 'ConcreteSubject' class
/// </summary>
class IBM : Stock
{
// Constructor - symbol for IBM is always same
public IBM(double price)
: base("IBM", price)
{
}
}
/// <summary>
/// The 'Observer' interface
/// 觀察者
/// </summary>
interface IInvestor
{
void Update(object sender, ChangeEventArgs e);
}
/// <summary>
/// The 'ConcreteObserver' class
/// </summary>
class Investor : IInvestor
{
// Gets or sets the investor name
public string Name { get; set; }
// Gets or sets the stock
public Stock Stock { get; set; }
public void Update(object sender, ChangeEventArgs e)
{
Console.WriteLine("Notified {0} of {1}'s " +
"change to {2:C}", Name, e.Symbol, e.Price);
}
}
}
State:狀態
- 封裝變化:物件的狀態
- 自悟:一個例項有不同的狀態,但是不同狀態的對外介面都是相同的,內部實現不一樣
- 意圖:允許一個物件在其內部狀態改變時改變它的行為。物件看起來似乎修改了它所屬的類。
- 適用條件:
- 一個物件的行為取決於它的狀態,並且它必須在執行時更具狀態改變它的行為。
- 一個操作中含有龐大的多分分支條件語句,且這些分支依賴於該物件的狀態,這個狀態通常用一個或多個列舉常量表示,多個操作包含這一相同的條件結構,State模式將每一個條件分支放入一個獨立的類中。
- 參與者職責:
- Context:環境
- 定義客戶感興趣的介面
- 維護一個ConcreteState子類的例項,這個例項定義當前狀態
- State:狀態
- 定義一個介面以封裝與Context的一個特定狀態相關的行為
- ConcreteState subclasses:具體狀態類
- 每一子類實現一個與Context的一個狀態相關的行為。
- Context:環境
- 協作
- Context將與狀態相關的請求委託給當前ConcreteState物件處理。
- Context可將自身作為一個引數傳遞給處理該請求的狀態物件,使得狀態物件在必要時可訪問Context
- Context是客戶適用的主要介面,客戶可用狀態物件來配置一個Context,一旦一個Context配置完畢,客戶不再需要直接與狀態物件打交道。
- Context和ConcreteState子類都可決定那個狀態是另外一個的後繼者
- 優勢和劣勢
- Flyweight解釋何時以及怎樣共享狀態物件。
- 類圖
- 舉例
Account將不同狀態的職責委託給State來做
namespace DesignPatterns.Behavioral.State.NETOptimized
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Open a new account
var account = new Account("Jim Johnson");
// Apply financial transactions
account.Deposit(500.0);
account.Deposit(300.0);
account.Deposit(550.0);
account.PayInterest();
account.Withdraw(2000.00);
account.Withdraw(1100.00);
}
}
/// <summary>
/// The 'State' abstract class
/// </summary>
abstract class State
{
protected double interest;
protected double lowerLimit;
protected double upperLimit;
// Gets or sets the account
public Account Account { get; set; }
// Gets or sets the balance
public double Balance { get; set; }
/// <summary>
/// 存款
/// </summary>
/// <param name="amount"></param>
public abstract void Deposit(double amount);
/// <summary>
/// 撤銷
/// 退回
/// </summary>
/// <param name="amount"></param>
public abstract void Withdraw(double amount);
/// <summary>
/// 支付利息
/// </summary>
public abstract void PayInterest();
}
/// <summary>
/// A 'ConcreteState' class
/// <remarks>
/// Red state indicates account is overdrawn
/// </remarks>
/// </summary>
class RedState : State
{
double serviceFee;
// Constructor
public RedState(State state)
{
Balance = state.Balance;
Account = state.Account;
Initialize();
}
private void Initialize()
{
// Should come from a datasource
interest = 0.0;
lowerLimit = -100.0;
upperLimit = 0.0;
serviceFee = 15.00;
}
/// 存款
public override void Deposit(double amount)
{
Balance += amount;
StateChangeCheck();
}
public override void Withdraw(double amount)
{
amount = amount - serviceFee;
Console.WriteLine("No funds available for withdrawal!");
}
public override void PayInterest()
{
// No interest is paid
}
private void StateChangeCheck()
{
if (Balance > upperLimit)
{
Account.State = new SilverState(this);
}
}
}
/// <summary>
/// A 'ConcreteState' class
/// 銀級賬戶
/// <remarks>
/// Silver state is non-interest bearing state
/// </remarks>
/// </summary>
class SilverState : State
{
// Overloaded constructors
public SilverState(State state) :
this(state.Balance, state.Account)
{
}
/// <summary>
/// 銀級賬戶
/// </summary>
/// <param name="balance"></param>
/// <param name="account"></param>
public SilverState(double balance, Account account)
{
Balance = balance;
Account = account;
Initialize();
}
private void Initialize()
{
// Should come from a datasource
interest = 0.0;
lowerLimit = 0.0;
upperLimit = 1000.0;
}
public override void Deposit(double amount)
{
Balance += amount;
StateChangeCheck();
}
public override void Withdraw(double amount)
{
Balance -= amount;
StateChangeCheck();
}
public override void PayInterest()
{
Balance += interest * Balance;
StateChangeCheck();
}
private void StateChangeCheck()
{
if (Balance < lowerLimit)
{
Account.State = new RedState(this);
}
else if (Balance > upperLimit)
{
Account.State = new GoldState(this);
}
}
}
/// <summary>
/// A 'ConcreteState' class
/// <remarks>
/// Gold incidates interest bearing state
/// </remarks>
/// </summary>
class GoldState : State
{
// Overloaded constructors
public GoldState(State state)
: this(state.Balance, state.Account)
{
}
public GoldState(double balance, Account account)
{
Balance = balance;
Account = account;
Initialize();
}
private void Initialize()
{
// Should come from a database
interest = 0.05;
lowerLimit = 1000.0;
upperLimit = 10000000.0;
}
public override void Deposit(double amount)
{
Balance += amount;
StateChangeCheck();
}
public override void Withdraw(double amount)
{
Balance -= amount;
StateChangeCheck();
}
public override void PayInterest()
{
Balance += interest * Balance;
StateChangeCheck();
}
private void StateChangeCheck()
{
if (Balance < 0.0)
{
Account.State = new RedState(this);
}
else if (Balance < lowerLimit)
{
Account.State = new SilverState(this);
}
}
}
/// <summary>
/// The 'Context' class
/// </summary>
class Account
{
string owner;
// Constructor
public Account(string owner)
{
// New accounts are 'Silver' by default
this.owner = owner;
this.State = new SilverState(0.0, this);
}
// Gets the balance
public double Balance
{
get { return State.Balance; }
}
// Gets or sets state
public State State { get; set; }
public void Deposit(double amount)
{
State.Deposit(amount);
Console.WriteLine("Deposited {0:C} --- ", amount);
Console.WriteLine(" Balance = {0:C}", this.Balance);
Console.WriteLine(" Status = {0}", this.State.GetType().Name);
Console.WriteLine("");
}
public void Withdraw(double amount)
{
State.Withdraw(amount);
Console.WriteLine("Withdrew {0:C} --- ", amount);
Console.WriteLine(" Balance = {0:C}", this.Balance);
Console.WriteLine(" Status = {0}\n", this.State.GetType().Name);
}
public void PayInterest()
{
State.PayInterest();
Console.WriteLine("Interest Paid --- ");
Console.WriteLine(" Balance = {0:C}", this.Balance);
Console.WriteLine(" Status = {0}\n", this.State.GetType().Name);
}
}
}
Strategy:策略 Policy
-
封裝變化:演算法
-
意圖:定有一系列的演算法,把它們一個個封裝起來,並且使它們可相互替換,本模式使得演算法的變化獨立於使用它的客戶。
-
適用條件:
- 許多相關的類僅僅是行為有異,策略提供了一種用多個行為中的一個行為來配置一個類的方法。
- 需要適用一個演算法的不同變體,可能會定義一些反映不同的空間/時間權衡的演算法,這些變體實現為一個演算法的類層次時,可以適用策略模式。
- 演算法適用客戶不應該知道的資料,可使用策略模式以避免暴露覆雜的,與演算法相關的資料結構
- 一個類定義多種行為,並且這些行為在這個類的操作中以多個條件語句的形式出現。將條件分支移入它們各自的Strategy類中以代替這些條件語句。
-
參與者職責:
- Strategy:定義所有支援的演算法的公共介面,Context使用這個介面來呼叫ConcreteStrategy定義的演算法。
- ConcreteStrategy:實現介面
- Context:用一個ConcreteStrategy物件來配置,維護一個對Strategy物件的引用,定義一個介面來讓strategy訪問它的資料。
-
協作
- strategy和context相互協作,當演算法被呼叫的時候,Context可以將演算法所需的資料傳遞給Strategy。
- client只跟context進行互動。
-
優勢和劣勢
- Flyweight:strategy物件經常是很好的輕量級物件。
-
類圖:
namespace DesignPatterns.Behavioral.Strategy.NETOptimized
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Two contexts following different strategies
var studentRecords = new SortedList()
{
new Student{ Name = "Samual", Ssn = "154-33-2009" },
new Student{ Name = "Jimmy", Ssn = "487-43-1665" },
new Student{ Name = "Sandra", Ssn = "655-00-2944" },
new Student{ Name = "Vivek", Ssn = "133-98-8399" },
new Student{ Name = "Anna", Ssn = "760-94-9844" },
};
studentRecords.SortStrategy = new QuickSort();
studentRecords.SortStudents();
studentRecords.SortStrategy = new ShellSort();
studentRecords.SortStudents();
studentRecords.SortStrategy = new MergeSort();
studentRecords.SortStudents();
}
}
/// <summary>
/// The 'Strategy' interface
/// </summary>
interface ISortStrategy
{
void Sort(List<Student> list);
}
/// <summary>
/// A 'ConcreteStrategy' class
/// </summary>
class QuickSort : ISortStrategy
{
public void Sort(List<Student> list)
{
// Call overloaded Sort
Sort(list, 0, list.Count - 1);
Console.WriteLine("QuickSorted list ");
}
// Recursively sort
void Sort(List<Student> list, int left, int right)
{
int lhold = left;
int rhold = right;
// Use a random pivot
var random = new Random();
int pivot = random.Next(left, right);
Swap(list, pivot, left);
pivot = left;
left++;
while (right >= left)
{
int compareleft = list[left].Name.CompareTo(list[pivot].Name);
int compareright = list[right].Name.CompareTo(list[pivot].Name);
if ((compareleft >= 0) && (compareright < 0))
{
Swap(list, left, right);
}
else
{
if (compareleft >= 0)
{
right--;
}
else
{
if (compareright < 0)
{
left++;
}
else
{
right--;
left++;
}
}
}
}
Swap(list, pivot, right);
pivot = right;
if (pivot > lhold) Sort(list, lhold, pivot);
if (rhold > pivot + 1) Sort(list, pivot + 1, rhold);
}
// Swap helper function
private void Swap(List<Student> list, int left, int right)
{
var temp = list[right];
list[right] = list[left];
list[left] = temp;
}
}
/// <summary>
/// A 'ConcreteStrategy' class
/// </summary>
class ShellSort : ISortStrategy
{
public void Sort(List<Student> list)
{
// ShellSort(); not-implemented
Console.WriteLine("ShellSorted list ");
}
}
/// <summary>
/// A 'ConcreteStrategy' class
/// </summary>
class MergeSort : ISortStrategy
{
public void Sort(List<Student> list)
{
// MergeSort(); not-implemented
Console.WriteLine("MergeSorted list ");
}
}
/// <summary>
/// The 'Context' class
/// </summary>
class SortedList : List<Student>
{
// Sets sort strategy
public ISortStrategy SortStrategy { get; set; }
// Perform sort
public void SortStudents()
{
SortStrategy.Sort(this);
// Display sort results
foreach (var student in this)
{
Console.WriteLine(" " + student.Name);
}
Console.WriteLine();
}
}
/// <summary>
/// Represents a student
/// </summary>
class Student
{
// Gets or sets student name
public string Name { get; set; }
// Gets or sets student social security number
public string Ssn { get; set; }
}
}
Template Method
- 封裝變化:演算法的某些步驟
- 意圖:定義一個操作中的演算法骨架,而將一些步驟延遲到子類中,Template Method 使得子類不改變一個演算法的介面即可重新定義該演算法的某些特定步驟。
- 適用條件:
- 一次性實現一個演算法的不變部分,可變部分留給子類實現。
- 各子類中公共的行為應被提取出來並幾種到一個公共父類中以避免程式碼重複。
- 參與者職責:
- AbstractClass:抽象類,定於抽象的原語操作,具體的子類將重定義它們以實現一個演算法的各步驟
- ConcreteClass具體類,實現原語操作以完成演算法中與特定子類相關的步驟
- 協作:
- ConcreteClass靠abstractclass來實現演算法中不變的步驟。
- 優勢和劣勢
- FactoryMethod:常被模板方法呼叫
- Strategy:模板方法適用繼承來改變演算法的一部分
- 類圖:
- 例項
public virtual void Connect()
{
// Make sure mdb is available to app
// 訪問資料檔案
//connectionString =
// "provider=Microsoft.JET.OLEDB.4.0; " +
// "data source=..\\..\\db1.mdb";
connectionString =
"provider=Microsoft.JET.OLEDB.4.0; " +
"data source=C:\\Users\\DuJinfeng\\Desktop\\Learn\\0CSharpSummary\\DCSharpPractises\\TestDesignPatterns\\db1.mdb";
}
/// <summary>
/// A 'ConcreteClass' class
/// </summary>
class Categories : DataAccessObject
{
public override void Select()
{
string sql = "SELECT CategoryName FROM Categories";
var dataAdapter = new OleDbDataAdapter(sql, connectionString);
dataSet = new DataSet();
dataAdapter.Fill(dataSet, "Categories");
}
public override void Process()
{
Console.WriteLine("Categories ---- ");
var dataTable = dataSet.Tables["Categories"];
foreach (DataRow row in dataTable.Rows)
{
Console.WriteLine(row["CategoryName"]);
}
Console.WriteLine();
}
}
Visitor:訪問
-
封裝變化:某些可作用於一個(組)物件上的操作,但不修改這些物件的類
-
意圖:表示一個作用於某物件結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作
-
適用條件
- Visitor必須定義兩個類層次,一個對應於接受操作的元素,另一個對應於定義對元素操作的訪問者。
- 一個物件結構包含很多類物件,它們有不同的介面,如果想對這些物件實時一些依賴於其具體類的操作
- 需要對一個物件結構中的物件進行很多不同並且不相關的操作。而且想避免這些操作汙染這些物件的類,
- 定義物件結構的類很少改變,但是需要經常在此結構上定義新的操作。改變物件結構類需要重定義對所有訪問者的介面,這需要很大的代價,所以封裝這部分操作。
-
參與者職責:
- Visitor:訪問者,為該物件結構中ConcreteElement的每個類宣告一個Visit操作。操作的名字和特徵標識傳送Visit請求給訪問者的類,使得訪問者可以確定正被訪問元素的具體類,這樣訪問者可以通過該元素的特定介面直接訪問它。
- ConcreteVisitor:具體訪問者,實現Visitor宣告的操作,每個操作實現演算法的一部分,concretevisitor為演算法提供上下文並存儲它的區域性狀態,這一狀態常在遍歷該結構的過程中累積。
- Element:定義一個Accept操作,以一個訪問者為引數。
- ConcreteElement:具體元素,實現Accept操作,該操作以一個訪問者為引數
- ObjectStructure:物件結構,能列舉它的元素,可以提供一個高層的介面以允許訪問者訪問它的元素。,可以是一個組合或集合列表
-
協作
- 一個適用visitor模式的客戶必須建立一個concretevisitor物件,然後遍歷該物件結構,並用該訪問者訪問每一個元素。
- 當一個元素被訪問時,呼叫對應於它的類visitor的操作。
-
優勢和劣勢
- composite:訪問者可以用於對一個Composite模式定義的物件結構進行操作
- Interpreter:訪問者可以用於解釋
- strategy封裝演算法
- state封裝有關狀態的行為
- mediator:封裝物件間的協議
- iterator物件封裝訪問和遍歷一個集合物件中的各個構件的方法。
-
類圖
Clerk:店員
Director:經理
president:總裁
namespace DesignPatterns.Behavioral.Visitor.NETOptimized
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Setup employee collection
var employee = new Employees();
employee.Attach(new Clerk());
employee.Attach(new Director());
employee.Attach(new President());
// Employees are 'visited'
employee.Accept(new IncomeVisitor());
employee.Accept(new VacationVisitor());
// 結果
// Clerk Hank's new income: ¥27,500.00
// Director Elly's new income: ¥38,500.00
// Director Elly's new vacation days: 19
}
}
/// <summary>
/// The 'Visitor' abstract class
/// </summary>
public abstract class Visitor
{
// Use reflection to see if the Visitor has a method
// named Visit with the appropriate parameter type
// (i.e. a specific Employee). If so, invoke it.
public void ReflectiveVisit(IElement element)
{
var types = new Type[] { element.GetType() };
var mi = this.GetType().GetMethod("Visit", types);
if (mi != null)
{
mi.Invoke(this, new object[] { element });
}
}
}
/// <summary>
/// A 'ConcreteVisitor' class
/// </summary>
class IncomeVisitor : Visitor
{
// Visit clerk
public void Visit(Clerk clerk)
{
DoVisit(clerk);
}
// Visit director
public void Visit(Director director)
{
DoVisit(director);
}
private void DoVisit(IElement element)
{
var employee = element as Employee;
// Provide 10% pay raise
employee.Income *= 1.10;
Console.WriteLine("{0} {1}'s new income: {2:C}",
employee.GetType().Name, employee.Name,
employee.Income);
}
}
/// <summary>
/// A 'ConcreteVisitor' class
/// </summary>
class VacationVisitor : Visitor
{
// Visit director
public void Visit(Director director)
{
DoVisit(director);
}
private void DoVisit(IElement element)
{
var employee = element as Employee;
// Provide 3 extra vacation days
employee.VacationDays += 3;
Console.WriteLine("{0} {1}'s new vacation days: {2}",
employee.GetType().Name, employee.Name,
employee.VacationDays);
}
}
/// <summary>
/// The 'Element' interface
/// </summary>
public interface IElement
{
void Accept(Visitor visitor);
}
/// <summary>
/// The 'ConcreteElement' class
/// </summary>
class Employee : IElement
{
// Constructor
public Employee(string name, double income,
int vacationDays)
{
this.Name = name;
this.Income = income;
this.VacationDays = vacationDays;
}
// Gets or sets name
public string Name { get; set; }
// Gets or set income
public double Income { get; set; }
// Gets or sets vacation days
public int VacationDays { get; set; }
public virtual void Accept(Visitor visitor)
{
visitor.ReflectiveVisit(this);
}
}
/// <summary>
/// The 'ObjectStructure' class
/// </summary>
class Employees : List<Employee>
{
public void Attach(Employee employee)
{
Add(employee);
}
public void Detach(Employee employee)
{
Remove(employee);
}
public void Accept(Visitor visitor)
{
// Iterate over all employees and accept visitor
this.ForEach(employee => employee.Accept(visitor));
Console.WriteLine();
}
}
// Three employee types
class Clerk : Employee
{
// Constructor
public Clerk()
: base("Hank", 25000.0, 14)
{
}
}
class Director : Employee
{
// Constructor
public Director()
: base("Elly", 35000.0, 16)
{
}
}
class President : Employee
{
// Constructor
public President()
: base("Dick", 45000.0, 21)
{
}
}
}
總結
責任鏈:多個物件處理一個請求。集合
命令:請求就是需求,將一個需求封裝成一個命令,集合
直譯器:解析某段文字或編碼,需要一個結合來存放所有直譯器,集合
迭代:實現不同物件的遍歷,集合
中介:公共聊天室,一個物件對一組物件的訂閱,集合
備忘錄:儲存物件某一個時刻狀態、
觀察者:事件訂閱
狀態:一個物件有不同的狀態,介面相同內部實現不一樣。
策略:將類的行為封裝,
模板方法:將演算法的骨架做為父類,在子類定義演算法細節
訪問者:需要對一個物件結構中的物件進行很多不同的並且不相關的操作,而需要避免讓這些操作"汙染"這些物件的類,也不希望在增加新操作時修改這些類。
Additional
Filter Pattern:過濾器模式
criteria:準則規範
這種模式允許開發人員使用不同的標準來過濾一組物件,通過邏輯運算以解耦的方式把它們連線起來。這種型別的設計模式屬於結構型模式,它結合多個標準來獲得單一標準。
Null Object Pattern
在空物件模式(Null Object Pattern)中,一個空物件取代 NULL 物件例項的檢查。Null 物件不是檢查空值,而是反應一個不做任何動作的關係。這樣的 Null 物件也可以在資料不可用的時候提供預設的行為。
MVC模式
業務代表模式
業務代表模式(Business Delegate Pattern)用於對錶示層和業務層解耦。它基本上是用來減少通訊或對錶示層程式碼中的業務層程式碼的遠端查詢功能。在業務層中我們有以下實體。
- 客戶端(Client) - 表示層程式碼可以是 JSP、servlet 或 UI java 程式碼。
- 業務代表(Business Delegate) - 一個為客戶端實體提供的入口類,它提供了對業務服務方法的訪問。
- 查詢服務(LookUp Service) - 查詢服務物件負責獲取相關的業務實現,並提供業務物件對業務代表物件的訪問。
- 業務服務(Business Service) - 業務服務介面。實現了該業務服務的實體類,提供了實際的業務實現邏輯。
組合實體模式
資料訪問物件模式
前端控制器模式
攔截過濾模式
ServiceLocator服務定位模式
Class A依賴ServiceA和ServiceB
有以下缺點
-
嘗試替換或跟新依賴項,必須更改類的原始碼並重新編譯。
-
依賴項的具體實現必須在編譯時可用
-
測試類非常困難,類對引用直接依賴,不能使用stub或mock
-
該類包含建立、定位和管理依賴項的重複程式碼
![img](ReadMe.assets/Mon, 29 Jun 2020 081813.png)
解決方案:
- 把類與依賴項解耦,從而使這些依賴項可被替換或者更新。
- 類在編譯時並不知道依賴項的具體實現。
- 類的隔離性和可測試性非常好。
- 類無需負責依賴項的建立、定位和管理邏輯。
- 通過將應用程式分解為鬆耦合的模組,達成模組間的無依賴開發、測試、版本控制和部署。
![img](ReadMe.assets/Mon, 29 Jun 2020 082952.png)
Service Locator 模式並不描述如何例項化服務,其描述了一種註冊和定位服務的方式。通常情況下,Service Locator 模式與工廠模式(Factory Pattern)和依賴注入模式(Dependency Injection Pattern)等結合使用。
服務定位器應該能夠在不知道抽象類的具體型別的情況下定位到服務。例如,它可能會使用字串或服務介面型別來影射服務,這允許在無需修改類的條件下替換依賴項的具體實現。
在使用 Service Locator 模式之前,請考慮以下幾點:
-
有很多程式中的元素需要管理。
-
在使用之前必須編寫額外的程式碼將服務的引用新增到服務定位器。
-
類將對服務定位器有依賴關係。
-
原始碼變的更加複雜和難以理解。
-
可以使用配置資料來定義執行時的關係。
-
必須提供服務的實現。因為服務定位器模式將服務消費者與服務提供者解耦,它可能需要提供額外的邏輯。這種邏輯將保證在服務消費者嘗試定位服務之前,服務提供者已被安裝和註冊。
![img](ReadMe.assets/Mon, 29 Jun 2020 084138.webp)
服務(Service) - 實際處理請求的服務。對這種服務的引用可以在 JNDI 伺服器中查詢到。
Context / 初始的 Context - JNDI Context 帶有對要查詢的服務的引用,實際是工廠。
服務定位器(Service Locator) - 服務定位器是通過 JNDI 查詢和快取服務來獲取服務的單點接觸。
快取(Cache) - 快取儲存服務的引用,以便複用它們。
客戶端(Client) - Client 是通過 ServiceLocator 呼叫服務的物件。
public interface Service { public String getName(); public void execute(); } public class Service1 implements Service { public void execute(){ System.out.println("Executing Service1"); } @Override public String getName() { return "Service1"; } } public class Service2 implements Service { public void execute(){ System.out.println("Executing Service2"); } @Override public String getName() { return "Service2"; } } public class InitialContext { public Object lookup(String jndiName){ if(jndiName.equalsIgnoreCase("SERVICE1")){ System.out.println("Looking up and creating a new Service1 object"); return new Service1(); }else if (jndiName.equalsIgnoreCase("SERVICE2")){ System.out.println("Looking up and creating a new Service2 object"); return new Service2(); } return null; } } import java.util.ArrayList; import java.util.List; public class Cache { private List<Service> services; public Cache(){ services = new ArrayList<Service>(); } public Service getService(String serviceName){ for (Service service : services) { if(service.getName().equalsIgnoreCase(serviceName)){ System.out.println("Returning cached "+serviceName+" object"); return service; } } return null; } public void addService(Service newService){ boolean exists = false; for (Service service : services) { if(service.getName().equalsIgnoreCase(newService.getName())){ exists = true; } } if(!exists){ services.add(newService); } } } public class ServiceLocator { private static Cache cache; static { cache = new Cache(); } public static Service getService(String jndiName){ Service service = cache.getService(jndiName); if(service != null){ return service; } InitialContext context = new InitialContext();//上下文相當於工廠 Service service1 = (Service)context.lookup(jndiName); cache.addService(service1); return service1; } } public class ServiceLocatorPatternDemo { public static void main(String[] args) { Service service = ServiceLocator.getService("Service1"); service.execute(); service = ServiceLocator.getService("Service2"); service.execute(); service = ServiceLocator.getService("Service1"); service.execute(); service = ServiceLocator.getService("Service2"); service.execute(); } } /*結果 Looking up and creating a new Service1 object Executing Service1 Looking up and creating a new Service2 object Executing Service2 Returning cached Service1 object Executing Service1 Returning cached Service2 object Executing Service2 */
傳輸物件模式
設計原則(SOLID)
單一職責原則(single responsibilities principle,SRP)
- 原理:一個類應該只有一個變化
- 分離職責:如果不耦合的職責那麼很簡單,如果兩個職責耦合,將兩個職責抽象為介面,通過繼承兩個介面將依賴關係抽離處理啊
開放封閉原則(open close principle,OCP)
- 軟體實體(類,模組,函式等)應該是可以擴充套件的,但是不可修改
- 對擴充套件開放:當需求改變時,對模組可以擴充套件。
- 對修改封閉:對模組進行擴充套件時,不必改動模組的原始碼或則二進位制程式碼,
- 僅僅抽象出容易變化的部分。
里氏替換原則(liskov substitution principle,LSP)
- 子型別必須能夠替換掉它的基型別。
介面隔離原則(interface segregation principle,ISP)
介面會被汙染:
當藉口的職責不再單一時,介面就很容易受到汙染。
一個常見的例子:一個門,可能是關著也可能是開著,而且門類知道只是是開著還是關著。
常見的介面設計,現在需要實現自動報警,當門開著超過一定的時間就進行報警。常見的方法是關聯timer類,實現報警。
這種方案就造成了介面汙染,所有的門都必須依賴timeclient,同時還會出現門檢測到時間超時,還未報警時,門關閉了,然後又被打開了,門變成了錯誤的報警
通過增加一個報警id,來區別每一次報警和客戶端。
- 介面隔離原則:不應該強迫客戶程式依賴並未使用的方法。
隔離介面
- 通過介面卡原則,實現timer類對door類的引用隔離doorclient.這樣僅僅增加了一個類,而將引用關係倒置。
建立一個派生自timer的timerclient物件,並把該物件請求委託給timerdoor。
這樣就實現了timer和door的隔離,即使對timer進行更改也不會影響到door。timerdoor也不需要和timerclient一樣的介面,
- 另一種方法是使用timerdoor來多重繼承,door和timerclient,
這種方案沒有多餘的開銷,只有當doortimeradapter物件所做的轉換是必須的時候或則不同的時候需要不同的轉換的時候,才需要使用介面卡方法。
例子:
AMT的一個例子,輸出資訊要轉換成不同的語言,輸出資訊要顯示在桌面上,
把不同的事務處理方法封裝在transaction上,這樣每個事務的修改都會造成UI的修改,如果把介面分解成不通的單獨介面,就可以避免
依賴倒置原則(dependence inversion principle,DIP)
- 高層模組不應該依賴於底層模組,二者都應該依賴抽象
- 抽象不應該依賴細節,細節應該依賴抽象
為什麼叫倒置,在傳統軟體開發中,總傾向於建立一些高層模組依賴底層模組,策略依賴細節的軟體結構。一個良好的面向物件程式,對傳統設計結構而言就被倒置了。
其實就是都依賴介面程式設計,高層依賴介面,細節依賴介面,這樣模組的改動不會影響其他模組。比較好的模組設計:
模組和模組間的依賴都是依賴介面。
- 倒置不僅僅是依賴關係的倒置,也是介面所有權的倒置,通常會認為工具庫應該擁有自己的介面,但其實應該是客戶擁有介面,而它們的服務者應該是介面的派生。著名的 holly wood原則:“Don't call us, we'll call you”不要呼叫我們,我們會呼叫你,低層模組實現在高層模組中宣告並被高層模組呼叫的介面
- 程式所有的依賴關係都應該終止與抽象
- 任何變數都不應該持有一個指向具體類的引用
- 任何類都不應該從具體類派生
- 任何方法都不應該重寫它的任何基類中已經實現的方法。
打包原則
大型系統的設計非常依賴於好的元件設計,這樣每個團隊只要關注於單個元件而不需要關注整個系統。
但類經常會和其他類發生依賴關係,這些依賴關係也會跨越元件的邊界。
- 在向元件中分配類時應該依據什麼原則
- 應該使用什麼設計原則來管理元件之間的關係
- 元件的設計應該先於類(自頂而下),還是設計應該先於元件(自底而上)
- 元件的實體以什麼方式存在
- 元件建立好後,用於何種目的
元件和元件間的依賴關係:不能依賴具體類。只能是具體依賴抽象,抽象依賴抽象。這樣就可以將影響將至最低。
前三個原則來指導如何將類劃分到包中,後三個原則來管理包之間的耦合(穩定)。元件之間的耦合越穩定就越好
重用釋出等價原則(reuse release equivalence principle,REP)
重用粒度就是釋出粒度:一個元件的重用粒度和釋出粒度一樣大,重用的任何東西必須被同時釋出和跟蹤,
重用性必然是基於元件的,所以可重用的元件必須包含可重用的類,因至少某些元件應該由一組可重用的類組成
一個類中的元件要麼都是可重用的,要麼都是不可重用的。
共同重用原則(common reuse principle , CRP)
一個元件中所有的類都應該是共同重用的,如果重用了元件中的一個類,那麼就要重用元件中的所有類。
這個原則可以幫助我們確定哪些類應該在一個元件中,相互之間沒有緊密聯絡的類不應該在同一個元件中。
共同封閉原則(common closure principle,CCP)
元件中所有的類對同一種性質的變化應該是共同封閉的,一個變化若對一個封閉的元件產生影響,則將對該元件中所有的類產生影響,而對其他元件不產生影響。類似於單一職責原則。
無環依賴原則(Acyclic-Dependencies Principle,ADP)
在元件的關係圖中不允許存在環。
解除依賴環的方法:提取抽象介面,通過實現介面來替換關聯。關聯和實現的依賴關係相反。
每週構建是錯誤,前4天專案成員各自構建,週五整合,隨著專案的規模增長會導致整合時間越來越長。最終會超過半周時間在整合
解除依賴環,使用依賴倒置
穩定依賴原則(Stable-Dependencies Principle,SIP)
朝著穩定的方向進行依賴。
被依賴的越多,該元件就越不可能改動,則越穩定。
穩定性度量:
穩定抽象原則(Stable-Abstractions Principle,SAP)
元件的抽象程度與其穩定性一致。
中間連線線稱為主序列。
到主序列的距離:
越為0 越好
有了度量和標準就讓我們劃分元件吧!!!
如何設計
- 寫出問題的描述:挑出動詞和名詞,進而建立相應的類和操作
- 關注系統的協作和職責關係建模
- 對現實世界建模,再將分析時的物件轉化至設計中。
如何選擇設計模式
- 考慮問題,
- 匹配設計模式的意圖以及相互之間的關聯,
- 選擇目的相似的模式,
- 考慮我的設計中哪些是可變的
怎樣適用設計模式
-
大致瀏覽一遍模式
-
研究相關模式的結構,參與者職責及協作
-
子類都使用泛型,泛型來包裝,這樣的方式
咖啡機
做一個使用咖啡機的軟體,驅動介面已經被寫好。
咖啡機的硬體包括:
- 加熱器加熱棒(開關)Boiler
- 保溫盤加熱棒(開關)Warmer
- 保溫盤感測器(保溫盤空,杯子空,杯子不空)WarmerPlate
- 加熱器感測器(有水,沒水)Boiler
- 衝煮按鈕(開關)
- 指示燈(開關)Indicater
- 減壓閥門(開關)Relief
咖啡機的衝煮流程:
- 咖啡機一次煮12杯咖啡,
- 咖啡加入過濾器,過濾器加入支架,支架放到咖啡機。
- 倒入12杯水到濾水器,按下衝煮,水杯加熱至沸騰,水蒸氣碰灑到過濾器,形成水滴到咖啡壺,咖啡壺發現有水保溫,
- 拿走水杯,停止工作。
硬體工程師提供的介面:
namespace CoffeeMakerApps.Api
{
/// <summary>
/// 保溫板狀態
/// </summary>
public enum WarmerPlateStatus {
WARMER_EMPTY,
POT_EMPTY,
POT_NOT_EMPTY
};
/// <summary>
/// 加熱器狀態
/// </summary>
public enum BoilerStatus {
EMPTY,
NOT_EMPTY
};
/// <summary>
/// 沖泡按鈕狀態
/// </summary>
public enum BrewButtonStatus {
PUSHED,
NOT_PUSHED
};
/// <summary>
/// 加熱器執行狀態
/// </summary>
public enum BoilerState {
ON,
OFF
};
/// <summary>
/// 保溫板執行狀態
/// </summary>
public enum WarmerState {
ON,
OFF
};
/// <summary>
/// 指示燈狀態
/// </summary>
public enum IndicatorState {
ON,
OFF
};
/// <summary>
/// 洩壓閥狀態
/// </summary>
public enum ReliefValveState {
OPEN,
CLOSED
}
public interface CoffeeMakerApi {
/// <summary>
/// 返回加熱器的狀態。 該感測器檢測壺的存在以及壺中是否有咖啡。
/// </summary>
/// <returns></returns>
WarmerPlateStatus GetWarmerPlateStatus();
/// <summary>
/// 該功能返回加熱器開關的狀態。 加熱器開關是一個浮動開關,可檢測鍋爐中是否有超過1/2杯水。
/// </summary>
/// <returns></returns>
BoilerStatus GetBoilerStatus();
/// <summary>
/// 此功能返回沖泡按鈕的狀態。 沖泡按鈕是一個瞬時開關,可以記住其狀態。 每次對該函式的呼叫都將返回記憶狀態,然後將該狀態重置為NOT_PUSHED。 因此,即使以非常慢的速度輪詢此功能,它也將檢測到何時按下衝煮按鈕。
/// </summary>
/// <returns></returns>
BrewButtonStatus GetBrewButtonStatus();
/// <summary>
/// 開關加熱器中的加熱元件
/// </summary>
/// <param name="s"></param>
void SetBoilerState(BoilerState s);
/// <summary>
/// 開關保溫元件
/// </summary>
/// <param name="boilerState"></param>
void SetWarmerState(WarmerState boilerState);
/// <summary>
/// 此功能可開啟或關閉指示燈。 在沖泡週期結束時,指示燈應亮起。 使用者按下衝煮按鈕時應將其關閉。
/// </summary>
/// <param name="s"></param>
void SetIndicatorState(IndicatorState s);
/// <summary>
/// 此功能開啟和關閉洩壓閥。 當該閥關閉時,鍋爐中的蒸汽壓力將迫使熱水噴灑在咖啡過濾器上。 當閥門開啟,鍋爐中的水不會噴灑到過濾器上。
/// </summary>
/// <param name="s"></param>
void SetReliefValveState(ReliefValveState s);
}
}
方案一
建立一個咖啡機超類,關聯各個硬體類。這個方案是非常醜陋的,這不是根據行為劃分的,有些類,比如light沒有任何變數,僅僅呼叫了驅動介面,這種類叫水蒸氣類。沒有意義
方案二
按照行為來劃分,要確定一些類有沒有意義,只需要確定誰使用他們,而且要到業務底層去看。把問題的本質和細節分離,忘掉一切,最根本的問題是如何煮咖啡。如何煮咖啡,將熱水倒到咖啡上,把沖泡好的咖啡收集起來。水從哪來?咖啡放到哪裡?那麼就有兩個類:熱水類和支架類,大大多數人會考慮熱水流到支架中,這是比較錯誤的,軟體的行為應該按照軟體的行為給出而不是基於一些物理關係。還需要考慮使用者介面,這樣就有三個類。
- 誰使用水蒸氣類,來確定基類
- 最原始的需求
- 軟體行為而不是物理行為,
左邊是物理關係,右邊是軟體行為,也就是ContainmentVessel依賴HotWaterSource。
用例
- 按下衝煮,詢問HotWaterSource,ContainmentVessel做好準備,都準備好就啟動HotWaterSource,a
- 正在煮咖啡的過程中,咖啡壺被拿走要暫停HotWaterSource,咖啡放回繼續HotWaterSource。b
- 衝煮完成,水流用完,容器滿了,c,d
- 咖啡喝完,空容器,通知使用者喝完,e
Containment Vessel:支架和保溫壺
Resume:恢復
a,b,c,d表示四種邏輯:
- a 表示:使用者按下衝煮,詢問支架中有咖啡壺,熱水器中有水,然後才開始煮咖啡
- b 表示:如果正在煮咖啡的過程中咖啡壺被拿走,則必須中斷水流,停止送熱水,再次放回咖啡壺繼續水流。
- c 表示:熱水器中感測器告訴我們水用完了就停止煮咖啡,同時告訴使用者和容器已經停止煮咖啡
- d表示:容器已經滿了,咖啡煮好,停止水流,向用戶傳送完成
- e 表示:咖啡喝完,一個空的咖啡壺放在支架上(保溫盤),熱水器應該知道這個訊息,同時使用者也應該知道這個訊息
這樣整個咖啡機的抽象就完成了,按職責劃分,各司其職。這三個抽象類不能知道任何關於咖啡機的任何資訊。這就是依賴倒置原則。
系統的控制流如何檢測感測器呢?是選擇執行緒還是輪詢。最好的總是假設訊息都是可以非同步傳送的,就像存在有獨立的執行緒一樣,把使用輪詢還是執行緒的決策推遲到最後一刻。
這樣設定了一個介面,main()程式就待在一個迴圈中,不停地一遍遍呼叫這個方法實現輪詢。
using CoffeeMakerApps.Api;
using CoffeeMakerApps.M4CoffeeMaker;
using System;
namespace CoffeeMakerApps
{
class M4CoffeeMakerRun
{
static void Main(string[] args) {
CoffeeMakerApi api = new M4CoffeeMakerApi();
UserInterface ui = new M4UserInterface(api);
HotWaterSource hws = new M4HotWaterSource(api);
ContainmentVessel cv = new M4ContainmentVessel(api);
ui.Init(hws,cv);
hws.Init(ui,cv);
cv.Init(ui,hws);
bool isGo=true;
while (isGo) {
((Pollable)ui).Poll();//poll:輪詢
((Pollable)hws).Poll();
((Pollable)cv).Poll();
}
Console.ReadKey();
}
}
}
依賴倒置,不允許高層的咖啡製作中依賴底層實現。
用例實現
用例一:
namespace CoffeeMakerApps.M4CoffeeMaker {
public abstract class UserInterface {
private HotWaterSource hws;
private ContainmentVessel cv;
protected bool isComplete;
protected UserInterface( bool isComplete) {
this.isComplete = isComplete;
}
public UserInterface() {
isComplete = true;
}
public void Init(HotWaterSource hws, ContainmentVessel cv)
{
this.hws = hws;
this.cv = cv;
}
public abstract void Done();
public abstract void ComleteCycle();
public void Complete() {
isComplete = true;
ComleteCycle();
}
protected void StartBrewing() {
if (hws.IsReady() && cv.IsReady()) {
isComplete = false;
hws.Start();
cv.Start();
}
}
}
}
using CoffeeMakerApps.Api;
namespace CoffeeMakerApps.M4CoffeeMaker {
public class M4UserInterface: UserInterface, Pollable {
private CoffeeMakerApi api;
public M4UserInterface(CoffeeMakerApi api) {
this.api = api;
}
public void Poll() {
BrewButtonStatus buttonStatus = api.GetBrewButtonStatus();
if(buttonStatus==BrewButtonStatus.PUSHED) StartBrewing();//Brewing釀造
}
public override void Done() {
api.SetIndicatorState(IndicatorState.ON);//設定指示燈狀態
}
public override void ComleteCycle() {
api.SetIndicatorState(IndicatorState.OFF);
}
}
}
namespace CoffeeMakerApps.M4CoffeeMaker {
public abstract class HotWaterSource {
private UserInterface ui;
private ContainmentVessel cv;
protected bool isBrewing;
public HotWaterSource() {
isBrewing = false;
}
public void Init(UserInterface ui, ContainmentVessel cv) {
this.ui = ui;
this.cv = cv;
}
public void Start() {
isBrewing = true;
StartBrewing();
}
public void Done() {
isBrewing = false;
}
protected void DeclareDone() {
ui.Done();
cv.Done();
isBrewing = false;
}
public abstract bool IsReady();
public abstract void StartBrewing();
public abstract void Pause();
public abstract void Resume();
}
}
using CoffeeMakerApps.Api;
namespace CoffeeMakerApps.M4CoffeeMaker {
public class M4HotWaterSource :HotWaterSource, Pollable {
private CoffeeMakerApi api;
public M4HotWaterSource(CoffeeMakerApi api)
{
this.api = api;
}
public override bool IsReady()
{
BoilerStatus boilerStatus = api.GetBoilerStatus();
return boilerStatus == BoilerStatus.NOT_EMPTY;
}
public override void StartBrewing()
{
api.SetReliefValveState(ReliefValveState.CLOSED);
api.SetBoilerState(BoilerState.ON);
}
public void Poll()
{
BoilerStatus boilerStatus = api.GetBoilerStatus();
if (isBrewing)
{
if (boilerStatus == BoilerStatus.EMPTY)
{
api.SetBoilerState(BoilerState.OFF);
api.SetReliefValveState(ReliefValveState.CLOSED);
DeclareDone();
}
}
}
public override void Pause()
{
api.SetBoilerState(BoilerState.OFF);
api.SetReliefValveState(ReliefValveState.OPEN);
}
public override void Resume()
{
api.SetBoilerState(BoilerState.ON);
api.SetReliefValveState(ReliefValveState.CLOSED);
}
}
}
namespace CoffeeMakerApps.M4CoffeeMaker {
public abstract class ContainmentVessel {
private UserInterface ui;
private HotWaterSource hws;
protected bool isBrewing;
protected bool isComplete;
public ContainmentVessel()
{
isBrewing = false;
isComplete = true;
}
public void Init(UserInterface ui, HotWaterSource hws)
{
this.ui = ui;
this.hws = hws;
}
public void Start()
{
isBrewing = true;
isComplete = false;
}
public void Done()
{
isBrewing = false;
}
protected void DeclareComplete()
{
isComplete = true;
ui.Complete();
}
protected void ContainerAvailable()
{
hws.Resume();
}
protected void ContainerUnavailable()
{
hws.Pause();
}
public abstract bool IsReady();
}
}
using CoffeeMakerApps.Api;
namespace CoffeeMakerApps.M4CoffeeMaker {
public class M4ContainmentVessel : ContainmentVessel, Pollable
{
private CoffeeMakerApi api;
private WarmerPlateStatus lastPotStatus;
public M4ContainmentVessel(CoffeeMakerApi api)
{
this.api = api;
lastPotStatus = WarmerPlateStatus.POT_EMPTY;
}
public override bool IsReady()
{
WarmerPlateStatus plateStatus =
api.GetWarmerPlateStatus();
return plateStatus == WarmerPlateStatus.POT_EMPTY;
}
public void Poll()
{
WarmerPlateStatus potStatus = api.GetWarmerPlateStatus();
if (potStatus != lastPotStatus)
{
if (isBrewing)
{
HandleBrewingEvent(potStatus);
}
else if (isComplete == false)
{
HandleIncompleteEvent(potStatus);
}
lastPotStatus = potStatus;
}
}
private void
HandleBrewingEvent(WarmerPlateStatus potStatus)
{
if (potStatus == WarmerPlateStatus.POT_NOT_EMPTY)
{
ContainerAvailable();
api.SetWarmerState(WarmerState.ON);
}
else if (potStatus == WarmerPlateStatus.WARMER_EMPTY)
{
ContainerUnavailable();
api.SetWarmerState(WarmerState.OFF);
}
else
{ // potStatus == POT_EMPTY
ContainerAvailable();
api.SetWarmerState(WarmerState.OFF);
}
}
private void
HandleIncompleteEvent(WarmerPlateStatus potStatus)
{
if (potStatus == WarmerPlateStatus.POT_NOT_EMPTY)
{
api.SetWarmerState(WarmerState.ON);
}
else if (potStatus == WarmerPlateStatus.WARMER_EMPTY)
{
api.SetWarmerState(WarmerState.OFF);
}
else
{ // potStatus == POT_EMPTY
api.SetWarmerState(WarmerState.OFF);
DeclareComplete();
}
}
}
}
輪詢咖啡機
namespace CoffeeMakerApps.M4CoffeeMaker {
public interface Pollable {
void Poll();
}
}
這個設計的好處
圈出來的三個類是系統的高層策略,圈內的類沒有依賴任何圈外的類,抽象和細節完全分離
測試
測試優先的原則:
using CoffeeMakerApps.Api;
using CoffeeMakerApps.M4CoffeeMaker;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestCoffeeMakerApps
{
internal class CoffeeMakerStub : CoffeeMakerApi {
public bool buttonPressed;
public bool lightOn;
public bool boilerOn;
public bool valveClosed;
public bool plateOn;
public bool boilerEmpty;
public bool potPresent;
public bool potNotEmpty;
public CoffeeMakerStub() {
buttonPressed = false;
lightOn = false;
boilerOn = false;
valveClosed = true;
plateOn = false;
boilerEmpty = true;
potPresent = true;
potNotEmpty = false;
}
public WarmerPlateStatus GetWarmerPlateStatus() {
if (!potPresent) return WarmerPlateStatus.WARMER_EMPTY;
else if (potNotEmpty) {
return WarmerPlateStatus.POT_NOT_EMPTY;
}
else {
return WarmerPlateStatus.POT_EMPTY;
}
}
public BoilerStatus GetBoilerStatus() {
return boilerEmpty ? BoilerStatus.EMPTY : BoilerStatus.NOT_EMPTY;
}
public BrewButtonStatus GetBrewButtonStatus() {
if (buttonPressed) {
buttonPressed = false;
return BrewButtonStatus.PUSHED;
}
else {
return BrewButtonStatus.NOT_PUSHED;
}
}
public void SetBoilerState(BoilerState boilerState) {
boilerOn = boilerState == BoilerState.ON;
}
public void SetWarmerState(WarmerState boilerState) {
plateOn = boilerState == WarmerState.ON;
}
public void SetIndicatorState(IndicatorState indicatorState) {
lightOn = indicatorState == IndicatorState.ON;
}
public void SetReliefValveState(ReliefValveState reliefValveState) {
valveClosed = reliefValveState == ReliefValveState.CLOSED;
}
}
[TestClass]
public class TestCoffeeMaker {
private M4UserInterface ui;
private M4HotWaterSource hws;
private M4ContainmentVessel cv;
private CoffeeMakerStub api;
[TestInitialize]
public void SetUp()
{
api = new CoffeeMakerStub();
ui = new M4UserInterface(api);
hws = new M4HotWaterSource(api);
cv = new M4ContainmentVessel(api);
ui.Init(hws, cv);
hws.Init(ui, cv);
cv.Init(ui, hws);
}
private void Poll()
{
ui.Poll();
hws.Poll();
cv.Poll();
}
[TestMethod]
public void InitialConditions()
{
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void StartNoPot()
{
Poll();
api.buttonPressed = true;
api.potPresent = false;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void StartNoWater()
{
Poll();
api.buttonPressed = true;
api.boilerEmpty = true;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void GoodStart()
{
NormalStart();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
private void NormalStart()
{
Poll();
api.boilerEmpty = false;
api.buttonPressed = true;
Poll();
}
[TestMethod]
public void StartedPotNotEmpty()
{
NormalStart();
api.potNotEmpty = true;
Poll();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsTrue(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void PotRemovedAndReplacedWhileEmpty()
{
NormalStart();
api.potPresent = false;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsFalse(api.valveClosed);
api.potPresent = true;
Poll();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void PotRemovedWhileNotEmptyAndReplacedEmpty()
{
NormalFill();
api.potPresent = false;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsFalse(api.valveClosed);
api.potPresent = true;
api.potNotEmpty = false;
Poll();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
private void NormalFill()
{
NormalStart();
api.potNotEmpty = true;
Poll();
}
[TestMethod]
public void PotRemovedWhileNotEmptyAndReplacedNotEmpty()
{
NormalFill();
api.potPresent = false;
Poll();
api.potPresent = true;
Poll();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsTrue(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void BoilerEmptyPotNotEmpty()
{
NormalBrew();
Assert.IsFalse(api.boilerOn);
Assert.IsTrue(api.lightOn);
Assert.IsTrue(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
private void NormalBrew()
{
NormalFill();
api.boilerEmpty = true;
Poll();
}
[TestMethod]
public void BoilerEmptiesWhilePotRemoved()
{
NormalFill();
api.potPresent = false;
Poll();
api.boilerEmpty = true;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsTrue(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
api.potPresent = true;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsTrue(api.lightOn);
Assert.IsTrue(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void EmptyPotReturnedAfter()
{
NormalBrew();
api.
potNotEmpty = false;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
}
}
薪水支付例項
該案例主要有做一個薪水支付系統,主要有三類員工
- 臨時工:基本時薪(Hourly),超過8小時加班時間1.5倍工資,每天有考勤卡,每週5結算。
- 正式員工:月薪(Salaried ),每個月最後一天結算。
- 經理:月薪,每月最後一天結算,有專案提成(Commission),每隔一週的週五結算,加入公會扣會費。
- 公會會費分服務費和會費:會費每週都有從薪水中扣除,服務費從下個月薪水中扣除。
- 薪水支付方式:可以選擇支票郵寄到家,支票儲存自取,直接存入銀行賬號。
- 薪水支付每天執行一次,在當天為相應的僱員進行支付,上一次支付到本次支付應付的數額。
用例:
- 新增僱員:僱員號,姓名,地址。(零時工,正式員工,經理)
- 刪除僱員:僱員號
- 登記考勤卡:僱員號,日期,小時
- 登記銷售憑條:僱員號,日期,銷售記錄
- 登記公會服務費:公會成員,服務費用
- 更改僱員細則:更改姓名,更改地址,更改每小時報酬,更改薪水,更改提成,更改支付方式,加入公會,離開公會
設計類和結構:
通過用例來推匯出應該有哪些類,不要陷入過多細節。
拿到一個新的專案,先分析專案有幾個模組,每個模組的用例,不要陷入細節,分而治之。
通過迭代的方式進行實現。資料庫是實現細節,應該盡推遲資料庫的設計。
從用例的角度設計
- 新增僱員
命令模式:
Hourly:小時工
Commissioned:正式員工
Balarid:經理
- 刪除僱員
把每一項工作劃分為自己的類中。這樣有可能會建立三個僱員類,但是分析一下就會發現變化的東西太多了,正式由於僱員變化的東西引發僱員型別的改變,只需要將變化的東西抽象出來,在更改僱員細則時改變這些變化的東西就可以改變僱員型別。
- 登記考勤卡
- 登記銷售憑條
- 登機工會服務費
- 更改僱員細則
這是由多個更改策略組合而成。
Affiliation:隸屬
- 發薪日
反思找到抽象
- 支付類別抽象
- 支付時間抽象
- 支付方式
- 從屬關係
實現
- 事務
事務是使用命令模式。
- 增加僱員事務,僱員有三個型別,所以使用模板模式來實現增加僱員,此處模板模式的唯一任務就是建立物件
使用模板方法來增加僱員。
using System;
namespace Payroll
{
public abstract class AddEmployeeTransaction : Transaction
{
private readonly int empid;
private readonly string name;
private readonly string address;
public AddEmployeeTransaction(int empid,
string name, string address, PayrollDatabase database)
: base (database)
{
this.empid = empid;
this.name = name;
this.address = address;
}
protected abstract
PaymentClassification MakeClassification();
protected abstract
PaymentSchedule MakeSchedule();
public override void Execute()
{
PaymentClassification pc = MakeClassification();
PaymentSchedule ps = MakeSchedule();
PaymentMethod pm = new HoldMethod();
Employee e = new Employee(empid, name, address);
e.Classification = pc;
e.Schedule = ps;
e.Method = pm;
database.AddEmployee(e);
}
public override string ToString()
{
return String.Format("{0} id:{1} name:{2} address:{3}", GetType().Name, empid, name,address);
}
}
}
namespace Payroll
{
public class AddSalariedEmployee : AddEmployeeTransaction
{
private readonly double salary;
public AddSalariedEmployee(int id, string name, string address, double salary, PayrollDatabase database)
: base(id, name, address, database)
{
this.salary = salary;
}
protected override
PaymentClassification MakeClassification()
{
return new SalariedClassification(salary);
}
protected override PaymentSchedule MakeSchedule()
{
return new MonthlySchedule();
}
}
}
- 刪除僱員
namespace Payroll
{
public class DeleteEmployeeTransaction : Transaction
{
private readonly int id;
public DeleteEmployeeTransaction(int id, PayrollDatabase database)
: base (database)
{
this.id = id;
}
public override void Execute()
{
database.DeleteEmployee(id);
}
}
}
提供僱員id,去資料庫刪除僱員,沒啥好說的。
-
考勤卡、銷售憑條、服務費
考勤卡:需要引數,僱員id,日期,工作時間
public class TimeCard
{
private readonly DateTime date;
private readonly double hours;
public TimeCard(DateTime date, double hours)
{
this.date = date;
this.hours = hours;
}
public double Hours
{
get { return hours; }
}
public DateTime Date
{
get { return date; }
}
}
using System;
namespace Payroll
{
public class TimeCardTransaction : Transaction
{
private readonly DateTime date;
private readonly double hours;
private readonly int empId;
public TimeCardTransaction(DateTime date, double hours, int empId, PayrollDatabase database)
: base(database)
{
this.date = date;
this.hours = hours;
this.empId = empId;
}
public override void Execute()
{
Employee e = database.GetEmployee(empId);
if (e != null)
{
HourlyClassification hc =
e.Classification as HourlyClassification;
if (hc != null)
hc.AddTimeCard(new TimeCard(date, hours));
else
throw new ApplicationException(
"Tried to add timecard to" +
"non-hourly employee");
}
else
throw new ApplicationException(
"No such employee.");
}
}
}
其他兩種與這類似
- 更改僱員屬性
更改僱員屬性由多個事務集合而成
改名字事務:
using System;
namespace Payroll
{
public abstract class ChangeEmployeeTransaction : Transaction
{
private readonly int empId;
public ChangeEmployeeTransaction(int empId, PayrollDatabase database)
: base (database)
{
this.empId = empId;
}
public override void Execute()
{
Employee e = database.GetEmployee(empId);
if(e != null)
Change(e);
else
throw new ApplicationException(
"No such employee.");
}
protected abstract void Change(Employee e);
}
}
namespace Payroll
{
public class ChangeNameTransaction
: ChangeEmployeeTransaction
{
private readonly string newName;
public ChangeNameTransaction(int id, string newName, PayrollDatabase database)
: base(id, database)
{
this.newName = newName;
}
protected override void Change(Employee e)
{
e.Name = newName;
}
}
}
更改僱員類別
namespace Payroll
{
public abstract class ChangeClassificationTransaction
: ChangeEmployeeTransaction
{
public ChangeClassificationTransaction(int id, PayrollDatabase database)
: base (id, database)
{}
protected override void Change(Employee e)
{
e.Classification = Classification;
e.Schedule = Schedule;
}
protected abstract
PaymentClassification Classification { get; }
protected abstract PaymentSchedule Schedule { get; }
}
}
namespace Payroll
{
public class ChangeHourlyTransaction
: ChangeClassificationTransaction
{
private readonly double hourlyRate;
public ChangeHourlyTransaction(int id, double hourlyRate, PayrollDatabase database)
: base(id, database)
{
this.hourlyRate = hourlyRate;
}
protected override PaymentClassification Classification
{
get { return new HourlyClassification(hourlyRate); }
}
protected override PaymentSchedule Schedule
{
get { return new WeeklySchedule(); }
}
}
}
public abstract class ChangeClassificationTransaction
: ChangeEmployeeTransaction
{
public ChangeClassificationTransaction(int id, PayrollDatabase database)
: base (id, database)
{}
protected override void Change(Employee e)
{
e.Classification = Classification;
e.Schedule = Schedule;
}
protected abstract
PaymentClassification Classification { get; }
protected abstract PaymentSchedule Schedule { get; }
}
public class ChangeHourlyTransaction
: ChangeClassificationTransaction
{
private readonly double hourlyRate;
public ChangeHourlyTransaction(int id, double hourlyRate, PayrollDatabase database)
: base(id, database)
{
this.hourlyRate = hourlyRate;
}
protected override PaymentClassification Classification
{
get { return new HourlyClassification(hourlyRate); }
}
protected override PaymentSchedule Schedule
{
get { return new WeeklySchedule(); }
}
}
public class ChangeSalariedTransaction : ChangeClassificationTransaction
{
private readonly double salary;
public ChangeSalariedTransaction(int id, double salary, PayrollDatabase database)
: base(id, database)
{
this.salary = salary;
}
protected override PaymentClassification Classification
{
get { return new SalariedClassification(salary); }
}
protected override PaymentSchedule Schedule
{
get { return new MonthlySchedule(); }
}
}
public class ChangeCommissionedTransaction
: ChangeClassificationTransaction
{
private readonly double baseSalary;
private readonly double commissionRate;
public ChangeCommissionedTransaction(int id, double baseSalary, double commissionRate, PayrollDatabase database)
: base(id, database)
{
this.baseSalary = baseSalary;
this.commissionRate = commissionRate;
}
protected override PaymentClassification Classification
{
get { return new CommissionClassification(baseSalary, commissionRate); }
}
protected override PaymentSchedule Schedule
{
get { return new BiWeeklySchedule(); }
}
}
改變方法和改變工會實現方式基本與改變僱傭類別相似
改變支付方法:
public abstract class ChangeMethodTransaction : ChangeEmployeeTransaction
{
public ChangeMethodTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{}
protected override void Change(Employee e)
{
PaymentMethod method = Method;
e.Method = method;
}
protected abstract PaymentMethod Method { get; }
}
public class ChangeMailTransaction : ChangeMethodTransaction
{
public ChangeMailTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{
}
protected override PaymentMethod Method
{
get { return new MailMethod("3.14 Pi St"); }
}
}
public class ChangeHoldTransaction : ChangeMethodTransaction
{
public ChangeHoldTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{
}
protected override PaymentMethod Method
{
get { return new HoldMethod(); }
}
}
public class ChangeDirectTransaction : ChangeMethodTransaction
{
public ChangeDirectTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{
}
protected override PaymentMethod Method
{
get { return new DirectDepositMethod("Bank -1", "123"); }
}
}
改變工會:
public abstract class ChangeAffiliationTransaction : ChangeEmployeeTransaction
{
public ChangeAffiliationTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{}
protected override void Change(Employee e)
{
RecordMembership(e);
Affiliation affiliation = Affiliation;
e.Affiliation = affiliation;
}
protected abstract Affiliation Affiliation { get; }
protected abstract void RecordMembership(Employee e);
}
public class ChangeUnaffiliatedTransaction : ChangeAffiliationTransaction
{
public ChangeUnaffiliatedTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{}
protected override Affiliation Affiliation
{
get { return new NoAffiliation(); }
}
protected override void RecordMembership(Employee e)
{
Affiliation affiliation = e.Affiliation;
if(affiliation is UnionAffiliation)
{
UnionAffiliation unionAffiliation =
affiliation as UnionAffiliation;
int memberId = unionAffiliation.MemberId;
database.RemoveUnionMember(memberId);
}
}
}
public class ChangeMemberTransaction : ChangeAffiliationTransaction
{
private readonly int memberId;
private readonly double dues;
public ChangeMemberTransaction(int empId, int memberId, double dues, PayrollDatabase database)
: base(empId, database)
{
this.memberId = memberId;
this.dues = dues;
}
protected override Affiliation Affiliation
{
get { return new UnionAffiliation(memberId, dues); }
}
protected override void RecordMembership(Employee e)
{
database.AddUnionMember(memberId, e);
}
}
- 支付薪水
支付月薪:
public class PaydayTransaction : Transaction
{
private readonly DateTime payDate;
private Hashtable paychecks = new Hashtable();
public PaydayTransaction(DateTime payDate, PayrollDatabase database)
: base (database)
{
this.payDate = payDate;
}
public override void Execute()
{
ArrayList empIds = database.GetAllEmployeeIds();
foreach(int empId in empIds)
{
Employee employee = database.GetEmployee(empId);
if (employee.IsPayDate(payDate))
{
DateTime startDate =
employee.GetPayPeriodStartDate(payDate);
Paycheck pc = new Paycheck(startDate, payDate);
paychecks[empId] = pc;
employee.Payday(pc);
}
}
}
public Paycheck GetPaycheck(int empId)
{
return paychecks[empId] as Paycheck;
}
}
public class MonthlySchedule : PaymentSchedule
{
private bool IsLastDayOfMonth(DateTime date)
{
int m1 = date.Month;
int m2 = date.AddDays(1).Month;
return (m1 != m2);
}
public bool IsPayDate(DateTime payDate)
{
return IsLastDayOfMonth(payDate);
}
public DateTime GetPayPeriodStartDate(DateTime date)
{
int days = 0;
while(date.AddDays(days - 1).Month == date.Month)
days--;
return date.AddDays(days);
}
public override string ToString()
{
return "monthly";
}
}
其中有paycheck類。該類有日期,總薪酬,服務扣費,實際薪酬
- 臨時工
public class HourlyClassification : PaymentClassification
{
private double hourlyRate;
private Hashtable timeCards = new Hashtable();
public HourlyClassification(double rate)
{
this.hourlyRate = rate;
}
public double HourlyRate
{
get { return hourlyRate; }
}
public TimeCard GetTimeCard(DateTime date)
{
return timeCards[date] as TimeCard;
}
public void AddTimeCard(TimeCard card)
{
timeCards[card.Date] = card;
}
public override double CalculatePay(Paycheck paycheck)
{
double totalPay = 0.0;
foreach(TimeCard timeCard in timeCards.Values)
{
if(DateUtil.IsInPayPeriod(timeCard.Date,
paycheck.PayPeriodStartDate,
paycheck.PayPeriodEndDate))
totalPay += CalculatePayForTimeCard(timeCard);
}
return totalPay;
}
private double CalculatePayForTimeCard(TimeCard card)
{
double overtimeHours = Math.Max(0.0, card.Hours - 8);
double normalHours = card.Hours - overtimeHours;
return hourlyRate * normalHours +
hourlyRate * 1.5 * overtimeHours;
}
public override string ToString()
{
return String.Format("${0}/hr", hourlyRate);
}
}
public class WeeklySchedule : PaymentSchedule
{
public bool IsPayDate(DateTime payDate)
{
return payDate.DayOfWeek == DayOfWeek.Friday;
}
public DateTime GetPayPeriodStartDate(DateTime date)
{
return date.AddDays(-6);
}
public override string ToString()
{
return "weekly";
}
}
public class Employee
{
private readonly int empid;
private string name;
private readonly string address;
private PaymentClassification classification;
private PaymentSchedule schedule;
private PaymentMethod method;
private Affiliation affiliation = new NoAffiliation();
public Employee(int empid, string name, string address)
{
this.empid = empid;
this.name = name;
this.address = address;
}
public string Name
{
get { return name; }
set { name = value; }
}
public string Address
{
get { return address; }
}
public PaymentClassification Classification
{
get { return classification; }
set { classification = value; }
}
public PaymentSchedule Schedule
{
get { return schedule; }
set { schedule = value; }
}
public PaymentMethod Method
{
get { return method; }
set { method = value; }
}
public Affiliation Affiliation
{
get { return affiliation; }
set { affiliation = value; }
}
public bool IsPayDate(DateTime date)
{
return schedule.IsPayDate(date);
}
public void Payday(Paycheck paycheck)
{
//計算總的薪資
double grossPay = classification.CalculatePay(paycheck);
//計算扣除薪資
double deductions = affiliation.CalculateDeductions(paycheck);
//計算實際薪資
double netPay = grossPay - deductions;
paycheck.GrossPay = grossPay;
paycheck.Deductions = deductions;
paycheck.NetPay = netPay;
//通過支付方式支付
method.Pay(paycheck);
}
public DateTime GetPayPeriodStartDate(DateTime date)
{
return schedule.GetPayPeriodStartDate(date);
}
public int EmpId
{
get { return empid; }
}
public override string ToString()
{
StringBuilder builder = new StringBuilder();
builder.Append("Emp#: ").Append(empid).Append(" ");
builder.Append(name).Append(" ");
builder.Append(address).Append(" ");
builder.Append("Paid ").Append(classification).Append(" ");
builder.Append(schedule);
builder.Append(" by ").Append(method);
return builder.ToString();
}
}
public class UnionAffiliation : Affiliation
{
private Hashtable charges = new Hashtable();
private int memberId;
private readonly double dues;
public UnionAffiliation(int memberId, double dues)
{
this.memberId = memberId;
this.dues = dues;
}
public UnionAffiliation()
: this(-1, 0.0)
{}
public ServiceCharge GetServiceCharge(DateTime time)
{
return charges[time] as ServiceCharge;
}
public void AddServiceCharge(ServiceCharge sc)
{
charges[sc.Time] = sc;
}
public double Dues
{
get { return dues; }
}
public int MemberId
{
get { return memberId; }
}
public double CalculateDeductions(Paycheck paycheck)
{
double totalDues = 0;
int fridays = NumberOfFridaysInPayPeriod(
paycheck.PayPeriodStartDate, paycheck.PayPeriodEndDate);
totalDues = dues * fridays;
foreach(ServiceCharge charge in charges.Values)
{
if(DateUtil.IsInPayPeriod(charge.Time,
paycheck.PayPeriodStartDate,
paycheck.PayPeriodEndDate))
totalDues += charge.Amount;
}
return totalDues;
}
private int NumberOfFridaysInPayPeriod(
DateTime payPeriodStart, DateTime payPeriodEnd)
{
int fridays = 0;
for (DateTime day = payPeriodStart;
day <= payPeriodEnd; day = day.AddDays(1))
{
if (day.DayOfWeek == DayOfWeek.Friday)
fridays++;
}
return fridays;
}
}
- 主程式
包分析
應用共同封閉原則CCP
Payroll Domain:包含了模型中所有的抽象。
最終編寫的大部分細節都被具有很少依賴者或者沒有依賴者的元件中。有依賴的元件都是抽象的
應用重用釋出等價原則(REP)
元件中的類應該是內聚的,它們之間相互依賴。類應該一同封閉,也應該一同重用。
事務類和它們要操作的元素分離
耦合封裝
TimeCard和SalesReceipe非常適合內部類
度量
物件工廠
Classifications和ClassificationTransactions之所以
通過工廠緩解過度依賴