值得收藏的 C# 設(shè)計(jì)模式套路之三
本文轉(zhuǎn)載自微信公眾號「老王Plus」,作者老王Plus的老王。轉(zhuǎn)載本文請聯(lián)系計(jì)老王Plus公眾號。
行為設(shè)計(jì)模式跟前兩種模式從內(nèi)容上是有區(qū)別的。行為設(shè)計(jì)模式更關(guān)注對象之間的通信,以及職責(zé)和任務(wù)的交互。
一、責(zé)任鏈
名稱起得很明顯, 就是一個(gè)鏈?zhǔn)降呢?zé)任或任務(wù)。為什么要鏈?zhǔn)侥?是因?yàn)檎埱笠刂鄠€(gè)處理程序往后傳遞。一個(gè)任務(wù),可能要分很多步,又不想把所有的步驟耦合到一個(gè)處理程序中處理,就會(huì)用到這個(gè)套路。
看看代碼:
- public interface IHandler
- {
- public IHandler SetNext(IHandler handler);
- public object Handle(object input);
- }
- public class Handler : IHandler
- {
- private IHandler _handler;
- public IHandler SetNext(IHandler handler)
- {
- _handler = handler;
- return handler;
- }
- public virtual object Handle(object input)
- {
- return _handler?.Handle(input);
- }
- }
- public class HandlerA : Handler
- {
- public override object Handle(object input)
- {
- if (input as string == "A")
- {
- Console.WriteLine("HandlerA : just return");
- return true;
- }
- Console.WriteLine("HandlerA : call next handler");
- return base.Handle(input);
- }
- }
- public class HandlerB : Handler
- {
- public override object Handle(object input)
- {
- if (input as string == "B")
- {
- Console.WriteLine("HandlerB : just return");
- return true;
- }
- Console.WriteLine("HandlerB : call next handler");
- return base.Handle(input);
- }
- }
- public class HandlerC : Handler
- {
- public override object Handle(object input)
- {
- if (input as string == "C")
- {
- Console.WriteLine("HandlerC : just return");
- return true;
- }
- Console.WriteLine("HandlerC : end");
- return base.Handle(input);
- }
- }
- public static class Example
- {
- public static void Test()
- {
- var handlerA = new HandlerA();
- var handlerB = new HandlerB();
- var handlerC = new HandlerC();
- handlerA.SetNext(handlerB).SetNext(handlerC);
- var resultOne = handlerA.Handle("A");
- var resultTwo = handlerA.Handle("B");
- var resultThree = handlerA.Handle("C");
- var resultFour = handlerA.Handle("D");
- }
- // results A:
- // HandlerA : just return
- // results B:
- // HandlerA : call next handler
- // HandlerB : just return
- // results C:
- // HandlerA : call next handler
- // HandlerB : call next handler
- // HandlerC : just return
- // results D:
- // HandlerA : call next handler
- // HandlerB : call next handler
- // HandlerC : end
- }
這里面,重要的是 handlerA.SetNext(handlerB).SetNext(handlerC) 一句。這個(gè)在限定鏈的方向和內(nèi)容。能理解到這一層,就算是真懂了。
二、命令
這個(gè)網(wǎng)上內(nèi)容很多,Command,通常會(huì)跟 Delegate、Event 一起說。
咱們這兒單說這個(gè)命令模式。
命令模式是一個(gè)非常常用的模式。它的作用,是把請求轉(zhuǎn)換為對象,以便我們可以異步、延遲、隊(duì)列或者參數(shù)化請求,以及做一些可撤銷的工作。
代碼套路特別簡單:
- public interface ICommand
- {
- public void Execute();
- }
- public class DemoCommand : ICommand
- {
- private readonly string _parameter;
- public DemoCommand(string parameter)
- {
- _parameter = parameter;
- }
- public void Execute()
- {
- Console.WriteLine(_parameter);
- }
- }
- public static class Invoker
- {
- public static void SendAction(ICommand command)
- {
- command.Execute();
- }
- }
- public static class Example
- {
- public static void Test()
- {
- var command = new DemoCommand("Hello WangPlus");
- Invoker.SendAction(command);
- }
- // results:
- // Hello WangPlus
- }
這個(gè) Command 的應(yīng)用場景特別多,建議大家理解透徹。我們在做 SDK 或 類庫的時(shí)候,會(huì)經(jīng)常有類庫內(nèi)實(shí)現(xiàn)業(yè)務(wù)邏輯,而調(diào)用端實(shí)現(xiàn)數(shù)據(jù)交互的情況,用的就是命令模式。舉個(gè)例子說:做個(gè)認(rèn)證授權(quán)的類庫,庫里面去實(shí)現(xiàn)鑒權(quán)和生成 Token 的工作,調(diào)用端去判斷登錄帳號密碼的驗(yàn)證。這樣做,這個(gè)庫才能是一個(gè)跟數(shù)據(jù)庫和帳號體系無關(guān)的通用庫。
三、迭代器
這也是一個(gè)用得很多的模式。
它最主要的作用,就是遍歷集合的元素;而最主要的特性,就是不會(huì)暴露數(shù)據(jù)本身。
看代碼:
- public abstract class IteratorBase
- {
- public abstract bool EndOfDocument();
- public abstract object Next();
- public abstract object First();
- public abstract object Current();
- }
- public class Iterator : IteratorBase
- {
- private readonly List<object> _customList;
- private int current = 0;
- public Iterator(List<object> customList)
- {
- _customList = customList;
- }
- public override bool EndOfDocument()
- {
- if (current >= _customList.Count - 1) return true;
- return false;
- }
- public override object Current()
- {
- return _customList[current];
- }
- public override object Next()
- {
- if (current < _customList.Count - 1) return _customList[++current];
- return null;
- }
- public override object First()
- {
- return _customList[0];
- }
- }
- public static class Example
- {
- public static void Test()
- {
- var demolist = new List<object>() { "a", "b", "c", "d" };
- var iterator = new Iterator(demolist);
- var item = iterator.First();
- while (item != null)
- {
- Console.WriteLine(item);
- item = iterator.Next();
- }
- if (iterator.EndOfDocument()) Console.WriteLine("Iterate done");
- }
- //results:
- // a
- // b
- // c
- // d
- // Iterate done
- }
如果想了解迭代器的原理、異步及更深的應(yīng)用,可以去看看我專門講迭代器的文章一文說通C#中的異步迭代器
四、解釋器
這也是行為模式中一個(gè)常用的模式,它主要是根據(jù)上下文來獲取不同的類型和行為。換句話說,相同的內(nèi)容,針對不同的類型,采取不同的行為。
通常這個(gè)模式,用得最多的是多語言的場景。
- public class Context
- {
- public string Value
- {
- get;
- private set;
- }
- public Context(string value)
- {
- Value = value;
- }
- }
- public abstract class Interpreter
- {
- public abstract void Interpret(Context context);
- }
- public class EnglishInterpreter : Interpreter
- {
- public override void Interpret(Context context)
- {
- switch (context.Value)
- {
- case "1":
- Console.WriteLine("One");
- break;
- case "2":
- Console.WriteLine("Two");
- break;
- }
- }
- }
- public class ChineseInterpreter : Interpreter
- {
- public override void Interpret(Context context)
- {
- switch (context.Value)
- {
- case "1":
- Console.WriteLine("一");
- break;
- case "2":
- Console.WriteLine("二");
- break;
- }
- }
- }
- public static class Example
- {
- public static void Test()
- {
- var interpreters = new List<Interpreter>() {
- new EnglishInterpreter(),
- new ChineseInterpreter()
- };
- var context = new Context("2");
- interpreters.ForEach(c => c.Interpret(context));
- }
- // results:
- // two
- // 二
- }
上面這個(gè)例子是解釋器的標(biāo)準(zhǔn)套路。通常我們用的時(shí)候,可以配合抽象工廠模式,根據(jù)上下文獨(dú)立加載單個(gè)的解釋器,這樣就能實(shí)現(xiàn)類似根據(jù)瀏覽器的設(shè)定語言來顯示界面語言的代碼。
如果用微軟的標(biāo)準(zhǔn)庫來實(shí)現(xiàn),那這個(gè)解釋器和抽象工廠已經(jīng)被包在了庫里,使用時(shí)只需定義語言對照表就成。但內(nèi)里的邏輯,還是這個(gè)。
五、中介
注意,是中介,不是中間件。這是兩個(gè)東西,別用混了。
不過,兩個(gè)原理上有一點(diǎn)相像。中介模式,目的是解耦對象之間的直接通信,并轉(zhuǎn)為從中介對象來傳遞消息。
也是看代碼:
- public interface IMediator
- {
- public void Send(string message, Caller caller);
- }
- public class Mediator : IMediator
- {
- public CallerA CallerA
- {
- get;
- set;
- }
- public CallerB CallerB
- {
- get;
- set;
- }
- public void Send(string message, Caller caller)
- {
- if (caller.GetType() == typeof(CallerA))
- {
- CallerB.ReceiveRequest(message);
- }
- else
- {
- CallerA.ReceiveRequest(message);
- }
- }
- }
- public abstract class Caller
- {
- protected readonly IMediator _mediator;
- public Caller(IMediator mediator)
- {
- _mediator = mediator;
- }
- }
- public class CallerA : Caller
- {
- public void SendRequest(string msg)
- {
- _mediator.Send(msg, this);
- }
- public void ReceiveRequest(string msg)
- {
- Console.WriteLine("CallerA Received : " + msg);
- }
- public CallerA(IMediator mediator) : base(mediator) { }
- }
- public class CallerB : Caller
- {
- public void SendRequest(string msg)
- {
- _mediator.Send(msg, this);
- }
- public void ReceiveRequest(string msg)
- {
- Console.WriteLine("CallerB Received : " + msg);
- }
- public CallerB(IMediator mediator) : base(mediator) { }
- }
- public static class Example
- {
- public static void Test()
- {
- var mediator = new Mediator();
- var callerA = new CallerA(mediator);
- var callerB = new CallerB(mediator);
- mediator.CallerA = callerA;
- mediator.CallerB = callerB;
- callerA.SendRequest("Hello");
- callerB.SendRequest("WangPlus");
- }
- // results:
- // CallerB Received : Hello
- // CallerA Received : WangPlus
- }
CallerA 和 CallerB 之間沒有直接通信,而是經(jīng)由中介 Mediator 進(jìn)行了消息傳遞。
這個(gè)模式,最常用的場景是兩個(gè)現(xiàn)成的類庫之間要實(shí)現(xiàn)通訊,而不想或沒辦法修改這兩個(gè)類庫的代碼,就可以做一個(gè)中介庫,來進(jìn)行數(shù)據(jù)傳遞。
六、備忘錄
跟名字一樣。備忘錄模式主要是用來保存對象的狀態(tài),并將狀態(tài)封裝,以便在需要時(shí),恢復(fù)到前邊的狀態(tài)。
套路是這樣的:
- public class Memento
- {
- private readonly string _state;
- public Memento(string state)
- {
- _state = state;
- }
- public string GetState()
- {
- return _state;
- }
- }
- public class Originator
- {
- public string State
- {
- get;
- set;
- }
- public Originator(string state)
- {
- State = state;
- }
- public Memento CreateMemento()
- {
- return new Memento(State);
- }
- public void RestoreState(Memento memento)
- {
- State = memento.GetState();
- }
- }
- public class Taker
- {
- private Memento _memento;
- public void SaveMemento(Originator originator)
- {
- _memento = originator.CreateMemento();
- }
- public void RestoreMemento(Originator originator)
- {
- originator.RestoreState(_memento);
- }
- }
- public static class Example
- {
- public static void Test()
- {
- var originator = new Originator("First State");
- var careTaker = new Taker();
- careTaker.SaveMemento(originator);
- Console.WriteLine(originator.State);
- originator.State = "Second State";
- Console.WriteLine(originator.State);
- careTaker.RestoreMemento(originator);
- Console.WriteLine(originator.State);
- }
- // results:
- // First State
- // Second State
- // First State
- }
這個(gè)代碼看著復(fù)雜,其實(shí)核心就一點(diǎn):在改變狀態(tài)前,先把狀態(tài)在對象之外保存下來。
你細(xì)品。
七、觀察者
觀察者模式主要處理的是對象間一對多的通信。如果一個(gè)對象的狀態(tài)發(fā)生了變化,依賴對象會(huì)發(fā)出通知并進(jìn)行更新。
- public class Updater
- {
- public string NewState
- {
- get;
- }
- private readonly List<ObserverBase> _observers = new List<ObserverBase>();
- public Updater(string newState)
- {
- NewState = newState;
- }
- public void AddObserver(ObserverBase observerBase)
- {
- _observers.Add(observerBase);
- }
- public void BroadCast()
- {
- foreach (var observer in _observers)
- {
- observer.Update();
- }
- }
- }
- public abstract class ObserverBase
- {
- public abstract void Update();
- }
- public class Observer : ObserverBase
- {
- private readonly string _name;
- public string State;
- private readonly Updater _updater;
- public Observer(string name, string state, Updater updater)
- {
- _name = name;
- State = state;
- _updater = updater;
- }
- public override void Update()
- {
- State = _updater.NewState;
- Console.WriteLine($"Observer {_name} State Changed to : " + State);
- }
- }
- public static class Example
- {
- public static void Test()
- {
- var updater = new Updater("WangPlus");
- updater.AddObserver(new Observer("1", "WangPlus1", updater));
- updater.AddObserver(new Observer("2", "WangPlus2", updater));
- updater.AddObserver(new Observer("3", "WangPlus3", updater));
- updater.BroadCast();
- }
- // results:
- // Observer 1 State Changed to : WangPlus
- // Observer 2 State Changed to : WangPlus
- // Observer 3 State Changed to : WangPlus
- }
好吧,這個(gè)代碼各上面?zhèn)渫浤J降拇a有點(diǎn)像。事實(shí)上,這兩個(gè)模式的主要區(qū)別,一個(gè)是一對一,一個(gè)是一對多。
至于為什么兩個(gè)模式的名稱區(qū)別這么大,說實(shí)話,我也不知道。在我的概念中,這兩個(gè)模式是可以混著用的。經(jīng)驗(yàn)來說,備忘錄模式我用得更多些。
八、狀態(tài)
狀態(tài)模式也是一個(gè)常用的模式。
狀態(tài)模式,和最前面的責(zé)任鏈模式,兩個(gè)有點(diǎn)類似。狀態(tài)模式更直接,就是狀態(tài)改變時(shí),同步改變行為。
這兩個(gè)模式,在很多情況下,可以有效減少 if…else 的分支數(shù)量。所以,看上去就又高大上了:)
上套路:
- public interface IState
- {
- public void Handle(Context context);
- }
- public class StateA : IState
- {
- public void Handle(Context context)
- {
- context.State = new StateB();
- }
- }
- public class StateB : IState
- {
- public void Handle(Context context)
- {
- context.State = new StateA();
- }
- }
- public class Context
- {
- private IState _state;
- public IState State
- {
- get => _state;
- set
- {
- _state = value;
- Console.WriteLine("State: " + _state.GetType().Name);
- }
- }
- public Context(IState state)
- {
- State = state;
- }
- public void Action()
- {
- State.Handle(this);
- }
- }
- public static class Example
- {
- public static void Test()
- {
- var context = new Context(new StateA());
- context.Action();
- context.Action();
- context.Action();
- context.Action();
- }
- // results:
- // State: StateA
- // State: StateB
- // State: StateA
- // State: StateB
- // State: StateA
- }
看懂了嗎?
如果把里面的 IState 換成 IHandler,那就是一個(gè)責(zé)任鏈。區(qū)別就是一個(gè)是數(shù)據(jù),一個(gè)是方法,除此之外都一樣。
所以,還是我老說的那句話:不要關(guān)心名稱,要關(guān)心實(shí)質(zhì)。在現(xiàn)代開發(fā)中,數(shù)據(jù)、方法、對象,其實(shí)都在趨向大一統(tǒng)的。明白這個(gè)道理,你就通了。
九、策略
策略模式主要是用來封裝算法族并使它們可互換,所以它們可以獨(dú)立地進(jìn)行更改,而不需要任何緊密耦合。
對,又是一個(gè)以解耦為目的的架構(gòu)。
- public interface IStrategy
- {
- public void AlgorithmAction();
- }
- public class AlgorithmStrategyA : IStrategy
- {
- public void AlgorithmAction()
- {
- Console.WriteLine("This is Algorithm A");
- }
- }
- public class AlgorithmStrategyB : IStrategy
- {
- public void AlgorithmAction()
- {
- Console.WriteLine("This is Algorithm B");
- }
- }
- public class Context
- {
- private readonly IStrategy _strategy;
- public Context(IStrategy strategy)
- {
- _strategy = strategy;
- }
- public void GeneralAction()
- {
- _strategy.AlgorithmAction();
- }
- }
- public static class Example
- {
- public static void Test()
- {
- var context = new Context(new AlgorithmStrategyA());
- context.GeneralAction();
- context = new Context(new AlgorithmStrategyB());
- context.GeneralAction();
- }
- // results:
- // This is Algorithm A
- // This is Algorithm A
- }
這個(gè)模式的核心是上下文中的 IStrategy,這本身是一個(gè)抽象,這個(gè)抽象對應(yīng)的實(shí)體里,才是算法的實(shí)現(xiàn)。
一個(gè)標(biāo)準(zhǔn)的模式。
十、模板
模板模式是面向?qū)ο蟮囊粋€(gè)基礎(chǔ)模式,應(yīng)用也非常廣。
這個(gè)模式類似于抽象工廠模式,在基類上建立整體的操作結(jié)構(gòu),并根據(jù)需求的變動(dòng),在子類中重寫一些操作。
聽著很復(fù)雜,其實(shí)代碼一看就明白:
- public abstract class TemplateBase
- {
- public void Operate()
- {
- FirstAction();
- SecondAction();
- }
- private void FirstAction()
- {
- Console.WriteLine("First action from template base");
- }
- protected virtual void SecondAction()
- {
- Console.WriteLine("Second action from template base");
- }
- }
- public class TemplateA : TemplateBase { }
- public class TemplateB : TemplateBase
- {
- protected override void SecondAction()
- {
- Console.WriteLine("Second action from template B");
- }
- }
- public class TemplateC : TemplateBase
- {
- protected override void SecondAction()
- {
- Console.WriteLine("Second action from template B");
- }
- }
- public static class Example
- {
- public static void Test()
- {
- var templateMethodA = new TemplateA();
- var templateMethodB = new TemplateB();
- var templateMethodC = new TemplateC();
- templateMethodA.Operate();
- templateMethodB.Operate();
- templateMethodC.Operate();
- }
- // results:
- // First action from template base
- // Second action from template base
- // First action from template base
- // Second action from template B
- // First action from template base
- // Second action from template B
- }
很簡單,對吧?
十一、訪問者
訪問者模式,是上面模板模式的一個(gè)變形,目的一樣,在不修改基類代碼的情況下,向子類的層次結(jié)構(gòu)中添加新的方法和行為。
看代碼:
- public interface IVisitor
- {
- public void VisitItem(ItemBase item);
- }
- public class VisitorA : IVisitor
- {
- public void VisitItem(ItemBase item)
- {
- Console.WriteLine("{0} visited by {1}", item.GetType().Name, this.GetType().Name);
- }
- }
- public class VisitorB : IVisitor
- {
- public void VisitItem(ItemBase item)
- {
- Console.WriteLine("{0} visited by {1}", item.GetType().Name, this.GetType().Name);
- }
- }
- public abstract class ItemBase
- {
- public abstract void Accept(IVisitor visitor);
- }
- public class ItemA : ItemBase
- {
- public override void Accept(IVisitor visitor)
- {
- visitor.VisitItem(this);
- }
- public void ExtraOperationA() { }
- }
- public class ItemB : ItemBase
- {
- public override void Accept(IVisitor visitor)
- {
- visitor.VisitItem(this);
- }
- public void ExtraOperationB() { }
- }
- public class StructureBuilder
- {
- readonly List<ItemBase> _items = new List<ItemBase>();
- public void AddItem(ItemBase element)
- {
- _items.Add(element);
- }
- public void Accept(IVisitor visitor)
- {
- foreach (var item in _items)
- {
- item.Accept(visitor);
- }
- }
- }
- public static class Example
- {
- public static void Test()
- {
- var structure = new StructureBuilder();
- structure.AddItem(new ItemA());
- structure.AddItem(new ItemB());
- structure.Accept(new VisitorA());
- structure.Accept(new VisitorB());
- }
- //results:
- //ItemA visited by VisitorA
- //ItemB visited by VisitorA
- //ItemA visited by VisitorB
- //ItemB visited by VisitorB
- }
訪問者模式擴(kuò)展了模板模式,擴(kuò)展性、復(fù)用性、靈活性更好,而且非常體現(xiàn)單一職責(zé)原則。
十二、總結(jié)
模式套路這就算是寫完了,居然用了三篇文章才寫完。
有沒有感覺?所有的模式,都是為了解耦。解耦的目的,是為了把一個(gè)系統(tǒng)分成更細(xì)的組件。細(xì)分組件更適合大團(tuán)隊(duì)的開發(fā)。而大團(tuán)隊(duì)的技術(shù)架構(gòu),更容易在網(wǎng)上的各種文章中有所體現(xiàn)。
所以,也跟大家提個(gè)醒:所有的架構(gòu)都是需要熟悉和掌握的,這叫面試必備的知識。在實(shí)際應(yīng)用中,如果架構(gòu)熟悉,所有的程序看著會(huì)更富有邏輯,而如果不熟悉架構(gòu),那看使用架構(gòu)寫出來的代碼,會(huì)是一場惡夢。
成長在于一點(diǎn)點(diǎn)的積累,于大家,于我,都一樣。