本系列將和大家分享面向對象23種設計模式中常用的幾種設計模式,本章主要簡單介紹下行為型設計模式。 ...
行為型設計模式:關註對象和行為的分離。(共11個)
甩鍋大法:把鍋丟出去,只管自己,哪管洪水滔天。把不穩定的地方移出去,自己只寫穩定的,能保證自身的穩定。
沒有什麼設計模式是完美無缺的,一個設計模式就是解決一類的問題的,通常設計模式在解決一類問題的同時,還會帶來別的問題,我們設計者要做的事兒,就是要揚長避短,充分發揮長處!
很多時候,可能會融合應用多個設計模式,分別解決對應的問題。
下麵我們結合幾種具體場景來分析幾種行為型設計模式。
1、模板方法模式(TemplateMethod Pattern)
模板方法設計模式:在基類父類定義流程,把可變邏輯分離到不同子類實現。
有個複雜的多步驟業務,定義一個父類(模板),模板負責完成流程,把步驟分解,固定不變的當前類--各不相同的子類--有的相同有的不同虛方法。
就是把部分行為做了分離。
好處:就是可以擴展,職責分明。
場景:銀行客戶端查詢餘額業務,它通常是包含幾個步驟,例如:第一是校驗用戶名和密碼,第二是查詢真實餘額,第三是計算利息,第四將最終信息展示給客戶。對於不同的客戶而言,這些步驟流程一般是固定不變的,但每一步的具體實現又有可能是不一樣的。比如普通客戶和VIP客戶計算利息的利率是不一樣的、活期和定期的利率也是不一樣的,又比如普通客戶和VIP客戶的信息是存在不同的資料庫而導致用戶名和密碼校驗的具體實現可能也是不同的。這時候就可以考慮使用模板方法設計模式了,在基類中定義流程,把可變邏輯分離到不同子類實現。
下麵我們來重點看下代碼:
抽象銀行客戶端:
using System; namespace TemplateMethodPattern { /// <summary> /// 抽象銀行客戶端 /// </summary> public abstract class AbstractClient { /// <summary> /// 查詢(模板方法) /// </summary> public void Query(int id, string name, string password) { if (this.CheckUser(id, password)) { double balance = this.QueryBalance(id); double interest = this.CalculateInterest(balance); this.Show(name, balance, interest); } else { Console.WriteLine("賬戶密碼錯誤"); } } /// <summary> /// 用戶名和密碼校驗 /// </summary> public virtual bool CheckUser(int id, string password) { return DateTime.Now < DateTime.Now.AddDays(1); } /// <summary> /// 查詢真實餘額 /// </summary> public virtual double QueryBalance(int id) { return new Random().Next(10000, 1000000); } /// <summary> /// 計算利息 /// 活期/定期/VIP 利率不同 /// </summary> public abstract double CalculateInterest(double balance); /// <summary> /// 信息展示 /// 有些一樣,有些不一樣 /// </summary> public virtual void Show(string name, double balance, double interest) { Console.WriteLine("尊敬的{0}客戶,你的賬戶餘額為:{1},利息為{2}", name, balance, interest); } } }
活期用戶:
using System; namespace TemplateMethodPattern { /// <summary> /// 銀行客戶端(活期用戶) /// </summary> public class ClientCurrent: AbstractClient { /// <summary> /// 活期利率不同 /// </summary> public override double CalculateInterest(double balance) { return balance * 0.001; } } }
定期用戶:
using System; namespace TemplateMethodPattern { /// <summary> /// 銀行客戶端(定期用戶) /// </summary> public class ClientRegular: AbstractClient { /// <summary> /// 定期利率不同 /// </summary> public override double CalculateInterest(double balance) { return balance * 0.003; } } }
VIP用戶:
using System; namespace TemplateMethodPattern { /// <summary> /// 銀行客戶端(VIP) /// </summary> public class ClientVip : AbstractClient { /// <summary> /// 計算利息 /// VIP利率不同 /// </summary> public override double CalculateInterest(double balance) { return balance * 0.005; } /// <summary> /// VIP的Show不一樣 /// </summary> public override void Show(string name, double balance, double interest) { Console.WriteLine("尊貴的{0} vip客戶,您的賬戶餘額為:{1},利息為{2}", name, balance, interest); } } }
使用如下:
using System; namespace TemplateMethodPattern { /// <summary> /// 模板方法設計模式 /// /// 有個複雜的多步驟業務,定義一個父類(模板),模板負責完成流程,把步驟分解,固定不變的當前類--各不相同的子類--有的相同有的不同虛方法。 /// 就是把部分行為做了分離。 /// 好處:就是可以擴展,職責分明。 /// 設計模式沒那麼神奇,只不過是把常用的東西跟場景結合,沉澱下來起個名字。 /// </summary> class Program { static void Main(string[] args) { try { { AbstractClient client = new ClientCurrent(); client.Query(101, "張三", "123456"); } { AbstractClient client = new ClientRegular(); client.Query(102, "李四", "000000"); } { AbstractClient client = new ClientVip(); client.Query(103, "王五", "251146"); } } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.ReadKey(); } } }
運行結果:
2、觀察者模式(Observer Pattern)
觀察者模式:一個對象動作觸發多個對象的行為(動作),通過觀察者可以去掉對象的依賴,支持各種自定義和擴展。
主題(Subject)發生變化是導致觀察者(Observer)觸發動作的根本原因。觀察者是觀察主題的。
場景:貓(主題)叫了一聲,觸發了觀察者的一系列動作,例如:老鼠跑、狗吼、雞叫、孩子哭等等。
下麵我們來重點看下代碼:
首先我們來看下不是使用觀察者模式實現的普通方法
using System; using System.Collections.Generic; using ObserverPattern.Observer; namespace ObserverPattern.Subject { /// <summary> /// 一隻神奇的貓 /// /// 貓叫一聲之後觸發一系列動作 /// 老鼠跑、狗吼、雞叫、孩子哭 /// </summary> public class Cat { /// <summary> /// 貓Miao了一聲 /// /// 依賴太多,任何一個對象改動,都會導致Cat變化。 /// 違背了單一職責,不僅自己miao,還要觸發各種動作,不穩定。 /// 加一個/減一個/調整順序 Cat都得改 /// Cat職責: /// 1、Miao 2、觸發一系列動作 這是需求 /// 實現上,這裡多了一個,3、指定動作 /// </summary> public void Miao() { Console.WriteLine("{0} Miao.....", this.GetType().Name); //依賴了 new Mouse().Run(); //老鼠跑 new Dog().Wang(); //狗吼 new Chicken().Woo(); //雞叫 new Baby().Cry(); //孩子哭 } } }
上面的這段代碼就很不穩定,依賴太多,任何一個對象改動,都會導致Cat變化。違背了單一職責,不僅自己miao,還要觸發各種動作,不穩定。加一個/減一個/調整順序 Cat都得改。
竟然Cat不穩定,那我們就可以考慮將這一堆對象甩出去,自己不寫讓別人傳遞。
接下來我們使用觀察者模式實現下:
首先定義一個介面,這個介面只是為了能夠使多個對象產生關係,方便保存和調用
using System; namespace ObserverPattern { /// <summary> /// 只是為了使多個對象產生關係,方便保存和調用 /// 方法本身其實沒用 /// </summary> public interface IObserver { void Action(); } }
讓我們的觀察者都去實現這個介面
using System; namespace ObserverPattern.Observer { /// <summary> /// 老鼠 /// </summary> public class Mouse : IObserver { public void Action() { this.Run(); } public void Run() { Console.WriteLine("{0} Run", this.GetType().Name); } } }
using System; namespace ObserverPattern.Observer { /// <summary> /// 狗 /// </summary> public class Dog : IObserver { public void Action() { this.Wang(); } public void Wang() { Console.WriteLine("{0} Wang", this.GetType().Name); } } }
using System; namespace ObserverPattern.Observer { /// <summary> /// 雞 /// </summary> public class Chicken : IObserver { public void Action() { this.Woo(); } public void Woo() { Console.WriteLine("{0} Woo", this.GetType().Name); } } }
using System; namespace ObserverPattern.Observer { /// <summary> /// 小孩 /// </summary> public class Baby : IObserver { public void Action() { this.Cry(); } public void Cry() { Console.WriteLine("{0} Cry", this.GetType().Name); } } }
我們的貓(主題)添加一個觀察者對象容器,讓外部去添加對象
using System; using System.Collections.Generic; namespace ObserverPattern.Subject { /// <summary> /// 一隻神奇的貓 /// /// 貓叫一聲之後觸發一系列動作 /// 老鼠跑、狗吼、雞叫、孩子哭 /// </summary> public class Cat { /// <summary> /// 存放觀察者的容器 /// </summary> private List<IObserver> _observerList = new List<IObserver>(); /// <summary> /// 添加觀察者 /// </summary> public void AddObserver(IObserver observer) { this._observerList.Add(observer); } /// <summary> /// 觀察者模式Miao /// </summary> public void MiaoObserver() { Console.WriteLine("{0} MiaoObserver.....", this.GetType().Name); if (this._observerList != null && this._observerList.Count > 0) { foreach (var item in this._observerList) { item.Action(); } } } } }
使用如下:
using System; using ObserverPattern.Observer; using ObserverPattern.Subject; namespace ObserverPattern { /// <summary> /// 觀察者模式 /// 對象和行為的分離 /// </summary> class Program { static void Main(string[] args) { try { { Console.WriteLine("***************Observer***************"); Cat cat = new Cat(); cat.AddObserver(new Mouse()); cat.AddObserver(new Chicken()); cat.AddObserver(new Baby()); cat.AddObserver(new Dog()); cat.AddObserver(new Mouse()); cat.MiaoObserver(); } } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.ReadKey(); } } }
運行結果:
上面的這種是標準的觀察者模式,是使用面向對象的方式實現的。其實觀察者模式還有一種更優雅的寫法,就是使用事件來實現的。如下所示:
using System; namespace ObserverPattern.Subject { /// <summary> /// 一隻神奇的貓 /// /// 貓叫一聲之後觸發一系列動作 /// 老鼠跑、狗吼、雞叫、孩子哭 /// </summary> public class Cat { private event Action MiaoHandler; public void MiaoEvent() { Console.WriteLine("{0} MiaoEvent.....", this.GetType().Name); if (this.MiaoHandler != null) { foreach (Action item in this.MiaoHandler.GetInvocationList()) { item.Invoke(); } } } } }
3、責任鏈模式(ResponsibilityChain Pattern)
責任鏈模式:請求的處理流程,沿著鏈子順序執行,還允許鏈子擴展和訂製。被稱為行為型設計模式的巔峰之作。
Context(上下文環境):用來保存業務處理中的參數--中間結果--最終結果。是行為型設計模式常用的標配。
為什麼說Context是行為型設計模式常用的標配呢?因為行為型設計模式關註的是對象和行為的分離,而方法(行為)在處理過程中就需要一些參數並且有返回值,這些信息就存放在Context上下文環境中。
場景:就以我們的請假審批流程為例,我們都知道不同的角色能夠審批的假期時長是不一樣的,例如:直接上級(SM)有權批1天的假期,主管(Charge)有權批3天的假期,經理(Manager)有權批7天的假期,總監(Chief)有權批30天假期,而我們的董事長(CEO)有權批300天的假期。下麵我們通過代碼來看下如何實現這個審批的功能。
首先我們來看下可能會想到的解決思路:
using System; namespace ResponsibilityChainPattern { class Program { static void Main(string[] args) { //請假申請 ApplyContext context = new ApplyContext() { Id = 1001, Name = "隔壁老王", Hour = 60, Description = "有急事需要請假幾天", AuditResult = false, AuditRemark = "" }; { AbstractAuditor sm = new SM() { Name = "直接上級" }; sm.Audit(context); if (!context.AuditResult) { AbstractAuditor charge = new Charge() { Name = "主管" }; charge.Audit(context); if (!context.AuditResult) { AbstractAuditor manager = new Manager() { Name = "經理" }; manager.Audit(context); if (!context.AuditResult) { //找下一環節 } } } if (context.AuditResult) { Console.WriteLine(context.AuditRemark); } else { Console.WriteLine("不幹了!"); } } } } }
其實上面的這段代碼我們只是按照面向對象的思路翻譯了一遍,完全沒有設計、沒有加工、沒有思考。這裡就有一個很大的弊端,比如:此處隔壁老王請假60小時,首先他去找直接上級請假,發現直接上級沒有批假許可權。然後他又去找主管請假,發現主管也沒有批假許可權。接著他又要去找經理、找總監請假,如此找下去直到找到有批假許可權人員為止,這就很不符合常理。正確的思路應該是這樣的,隔壁老王直接將請假申請交給直接上級審批,對於直接上級來說,如果是許可權範圍內則審批通過,如果許可權範圍外則轉交下一環節審批。下一環節的審批人員也是如此。最終我們就可以將請求的處理流程,沿著鏈子順序執行。
接下來我們使用責任鏈模式來實現下這個功能:
請假申請上下文:
using System; namespace ResponsibilityChainPattern { /// <summary> /// 請假申請 /// Context(上下文環境):用來保存業務處理中的參數--中間結果--最終結果。是行為型設計模式常用的標配。 /// 把行為轉移 /// </summary> public class ApplyContext { public int Id { get; set; } /// <summary> /// 姓名 /// </summary> public string Name { get; set; } /// <summary> /// 請假時長 /// </summary> public int Hour { get; set; } /// <summary> /// 請假理由 /// </summary> public string Description { get; set; } /// <summary> /// 審核結果 /// </summary> public bool AuditResult { get; set; } /// <summary> /// 審核備註 /// </summary> public string AuditRemark { get; set; } } }
抽象審核人員:
using System; namespace ResponsibilityChainPattern { /// <summary> /// 抽象審核人員 /// </summary> public abstract class AbstractAuditor { public string Name { get; set; } /// <summary> /// 審核 /// </summary> public abstract void Audit(ApplyContext context); /// <summary> /// 下一個審核者 /// </summary> private AbstractAuditor _nextAuditor = null; /// <summary> /// 指定下一個審核者 /// </summary> public void SetNext(AbstractAuditor auditor) { this._nextAuditor = auditor; } /// <summary> /// 交給下一個審核人員審核 /// </summary> protected void AuditNext(ApplyContext context) { if (this._nextAuditor != null) { this._nextAuditor.Audit(context); } else { context.AuditResult = false; context.AuditRemark = "不允許請假!"; } } } }
下麵我們不同角色的審核人員都去繼承抽象的審核人員,審核職責是:許可權範圍內則審批通過,許可權範圍外則轉交下一環節審批。
using System; namespace ResponsibilityChainPattern { /// <summary> /// 直接上級 /// /// 職責問題: /// 1、許可權範圍內,審批通過。 /// 2、許可權範圍外,轉交下一環節審批。 /// </summary> public class SM : AbstractAuditor { public override void Audit(ApplyContext context) { Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit"); if (context.Hour <= 8) { context.AuditResult = true; context.AuditRemark = "允許請假!"; } else { base.AuditNext(context); //轉交下一環節審批 } } } }
using System; namespace ResponsibilityChainPattern { /// <summary> /// 主管 /// </summary> public class Charge : AbstractAuditor { public override void Audit(ApplyContext context) { Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit"); if (context.Hour <= 24) { context.AuditResult = true; context.AuditRemark = "允許請假!"; } else { base.AuditNext(context); //轉交下一環節審批 } } } }
using System; namespace ResponsibilityChainPattern { /// <summary> /// 經理 /// </summary> public class Manager : AbstractAuditor { public override void Audit(ApplyContext context) { Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit"); if (context.Hour <= 56) { context.AuditResult = true; context.AuditRemark = "允許請假!"; } else { base.AuditNext(context); //轉交下一環節審批 } } } }
using System; namespace ResponsibilityChainPattern { /// <summary> /// 總監 /// </summary> public class Chief : AbstractAuditor { public override void Audit(ApplyContext context) { Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit"); if (context.Hour <= 240) { context.AuditResult = true; context.AuditRemark = "允許請假!"; } else { base.AuditNext(context); //轉交下一環節審批 } } } }
using System; namespace ResponsibilityChainPattern { /// <summary> /// 董事長 /// </summary> public class CEO : AbstractAuditor { public override void Audit(ApplyContext context) { Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit"); if (context.Hour <= 2400) { context.AuditResult = true; context.AuditRemark = "允許請假!"; } else { base.AuditNext(context); //轉交下一環節審批 } } } }
最後來看下如何使用:
using System; namespace ResponsibilityChainPattern { /// <summary> /// 建造者 /// </summary> public class AuditorBuilder { public static AbstractAuditor Build() { AbstractAuditor sm = new SM() { Name = "直接上級" }; AbstractAuditor charge = new Charge() { Name = "主管" }; AbstractAuditor manager = new Manager() { Name = "經理" }; AbstractAuditor chief = new Chief() { Name = "總監" }; AbstractAuditor ceo = new CEO() { Name = "董事長" }; //轉交下一環節,允許鏈子擴展和訂製 sm.SetNext(charge); charge.SetNext(manager); manager.SetNext(chief); chief.SetNext(ceo); return sm; } } }
using System; namespace ResponsibilityChainPattern { /// <summary> /// 責任鏈模式:請求的處理流程,沿著鏈子順序執行,還允許鏈子擴展和訂製。被稱為行為型設計模式的巔峰之作。 /// </summary> class Program { static void Main(