決戰設計模式 第三天筆記(Design Patterns)
- Command(命令模式)
- Macro Command
- Bridge(橋接模式)
- Observer(觀察者模式)
- Binding
- Mediator(中介者/協調者模式)
Command(命令模式)
目的:將一個請求封裝成一個物件,讓你能夠使用各種不同的訊息、佇列、紀錄以及支援復原功能加以參數化。
運用情境:在程式的運作過程中,很像在linux的cmd model內一直key指令,一行指令會執行一個或多個動作,程式撰寫過程中也把執行動作封裝成一個物件,然後依序執行就是Command Pattern。
//第一步建立要執行Command的指令,CheckFirstDate、CheckHead。兩組指令
public interface IFormatChecker
{
CheckResult CheckFirstDate(string source);
CheckResult CheckHead(string source);
}
//第二步執行check動作的主要執行人,同時就是實做check內容。
public class FormatChecker : IFormatChecker
{
public CheckResult CheckLength(string source)
{
var result = (source.Length == 29);
return new CheckResult() { Source = source, Result = result };
}
public CheckResult CheckHead(string source)
{
string head = source.Substring(0, 3);
var result = (head == "965");
return new CheckResult() { Source = source, Result = result };
}
}
//第三步抽出checker的執行操作,就是Execute的方法
public abstract class CheckCommand
{
protected FormatChecker Checker
{ get; private set; }
protected CheckCommand(FormatChecker checker)
{
Checker = checker;
}
public abstract CheckResult Execute(string source);
}
//這裡就開始依照開出的check方法一對一對的寫出對應的類別(class)
public class CheckLengthCommand : CheckCommand
{
public CheckLengthCommand(FormatChecker checker) : base(checker)
{
}
public override CheckResult Execute(string source)
{
return Checker.CheckLength(source);
}
}
//這裡就開始依照開出的check方法一對一對的寫出對應的類別(class)
public class CheckHeadCommand : CheckCommand
{
public CheckHeadCommand(FormatChecker checker) : base(checker)
{
}
public override CheckResult Execute(string source)
{
return Checker.CheckHead(source);
}
}
//第四步建立執行動作的工作站,所有command都會提交由Invoker來做執行。
public class Invoker
{
private List<CheckCommand> _commands = new List<CheckCommand>();
public void AddCommand(CheckCommand command)
{
_commands.Add(command);
}
public void RemoveCommand(CheckCommand command)
{
_commands.Remove(command);
}
public CheckResult Action(string source)
{
CheckResult result = null;
foreach (var command in _commands)
{
result = command.Execute(source);
if (result.Result == false)
{
break;
}
}
return result;
}
}
public class Client
{
//User端實際將命令加入Invoker中,也等於很像寫腳本的意思,把程式執行腳本寫好。
public static Invoker CreateInvoker()
{
FormatChecker checker = new FormatChecker();
Invoker invoker = new Invoker();
invoker.AddCommand(new CheckLengthCommand(checker));
invoker.AddCommand(new CheckHeadCommand(checker));
return invoker;
}
}
static void Main(string[] args)
{
//建立回傳結果List
List<CheckResult> results = new List<CheckResult>();
//產生執行命令的腳本
Invoker invoker = Client.CreateInvoker();
foreach (var item in FakeDataSource.Data)
{
//實際帶入需要被檢查的數據代入Action當中,取得回傳的檢查結果。
results.Add(invoker.Action(item));
}
foreach (var item in results)
{
Console.WriteLine($"Source : {item.Source } , Result ={item.Result}");
}
Console.ReadLine();
}
Macro Command
目的:巨集式的命令,這樣情境通常是一個Command 呼叫多個Receivers 來協同完成工作。
在上一節中Command當中每一個Check實做都是單一功能,但是今天想要讓一個Command具有多個功能就是巨集命令的概念。
//單一類別Command的執行內容只有一行CheckLength功能
public class CheckLengthCommand : CheckCommand
{
public CheckLengthCommand(FormatChecker checker) : base(checker)
{
}
public override CheckResult Execute(string source)
{
return Checker.CheckLength(source);
}
}
//Marco Command 單一類別具有多項功能
internal class Base64AesWriteCommand : IFileWriteCommand
{
private Base64Processor _base64Processor;
private AESCryptoProcessor _aesProcessor;
private FileProcess _fileprocess;
public Base64AesWriteCommand()
{
_aesProcessor = new AESCryptoProcessor();
_base64Processor = new Base64Processor();
_fileprocess = new FileProcess();
}
public void Execute(string path, byte[] data)
{
_fileprocess.Write(path, _aesProcessor
.EncryptData(_base64Processor.Encode(data)));
}
}
實際對於Invoker來說不管是單一Command或是Marco Command都是負責執行Execute的方法,但是Execute的執行內容,是多項Command還是單一Command就是巨集命令與單一命令的差異點。
Bridge(橋接模式)
目的:將抽象與實作解耦合,使得兩邊可以獨立的變化。
橋接模式可以說是Design Pattern的根源,在所有的Pattern中都會有依賴抽象的概念,Bridge橋接模式就是實踐抽象的概念,在類別中留下洞洞等執行個體來插入。
public class Human
{
//留下一個洞洞由外面來決定BMI結果判定
private IBMIComment _comment;
public Human(IBMIComment comment)
{
_comment = comment;
}
public Double Weight
{ get; set; }
public Double Height
{ get; set; }
private Boolean _calculated = false;
private Double _bmi = 0;
public Double BMI
{
get
{
if (!_calculated)
{
GetBMIValue();
}
return _bmi;
}
}
public String Result
{ get { return GetResult(); } }
private void GetBMIValue()
{
_calculated = true;
if (Weight > 0 && Height > 0)
{ _bmi = Weight / Math.Pow(Height, 2); }
else
{ _bmi = -1; }
}
//留下一個洞洞由外面來決定BMI結果判定
public String GetResult()
{
return _comment.GetResult(BMI);
}
}
public interface IBMIComment
{
string GetResult(double bmi);
}
public class ManComment : IBMIComment
{
public string GetResult(double bmi)
{
if (bmi > 25)
{
return "太胖";
}
else if (bmi < 20)
{
return "太瘦";
}
else
{
return "適中";
}
}
}
public class WomanComment : IBMIComment
{
public string GetResult(double bmi)
{
if (bmi > 22)
{
return "太胖";
}
else if (bmi < 18)
{
return "太瘦";
}
else
{
return "適中";
}
}
}
在這BMI的例子當中,BMI整體的計算過程與所需要的參數都是固定,唯一變動的部分只有評語的結果有所不同,這時候就把評語的地方由外面注入,這樣就達到Bridge Pattern的概念,換言之就是讓他可以變動抽換。
Observer(觀察者模式)
目的:定義一個一對多的物件依存關係,讓物件狀態一有變動,就自動通知其他的相依物件執行更新的動作。
Observer本身其實就是Event trigger,在C#當中有Event的方法可以讓多個Function去註冊,那假設不用C# Event如何靠類別與抽象實做出Event的功能。
/// <summary>
/// 實作觀察者
/// </summary>
public class ConcreteObserver : IObserver
{
public string Name { get; set; }
private ConcreteSubject _subject;
public ConcreteObserver(ConcreteSubject subject)
{
_subject = subject;
_subject.AddObserver(this);
}
public void Update()
{
Console.WriteLine(string.Format("{0} Update Subject State : {1}"
, Name, _subject.SubjectState));
}
}
/// <summary>
/// 觀察者的抽象
/// </summary>
public interface IObserver
{
void Update();
}
/// <summary>
/// 被觀察者(通知者)的抽象
/// </summary>
public abstract class Subject
{
private List<IObserver> _observers;
public Subject()
{
_observers = new List<IObserver>();
}
public void AddObserver(IObserver observer)
{
_observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
_observers.Remove(observer);
}
protected void Notify()
{
foreach (var observer in _observers)
{
observer.Update();
}
}
}
/// <summary>
/// 實作通知者
/// </summary>
public class ConcreteSubject : Subject
{
private string _subjectState;
public string SubjectState
{
get { return _subjectState; }
set
{
if (value != _subjectState)
{
_subjectState = value;
Notify();
}
}
}
}
static void Main(string[] args)
{
ConcreteSubject s = new ConcreteSubject();
ConcreteObserver o1 = new ConcreteObserver(s) { Name = "A" };
ConcreteObserver o2 = new ConcreteObserver(s) { Name = "B" };
s.SubjectState = "This is a dog";
Console.ReadLine();
}
我們從Main主程式開始講解,ConcreteSubject就是一個發布者,可以想像他就是廣播的電台,現在方有o1與o2都是兩台電視的socket join進去ConcreteSubject這個頻道中。
在ConcreteObserver的建構式中就將自己的執行個體加入到ConcreteSubject中,這樣電視台收看人的清單中加了這一位o1的收看者。
之後只要ConcreteSubject的SubjectState有更新的時候就會觸動Notify();,執行Notify();就會將所有收看者的清單拿出來foreach去通知每一個人(Subject Class)。
Mediator(中介者/協調者模式)
目的:定義一個中介物件,將另外一群物件的互動方式封裝起來,中介者使得這一群物件之間可以不需要直接認識對方,也不需要直接互動,降低他們的耦合關係,並且能夠獨立地改變他們之間的互動操作。
一張圖片就可以貫穿Mediator Pattern,前一節聊到Observer,是單一的電話兩端各自拉電話線,這樣子並沒有集中管理的概念,Mediator就是一位接線生,具有管理所有連線的能力,對於Client而言他唯一的實體電話線只有連到中華電信即可以通到各地。
public class Mediator
{
private Person Data
{ get; set; }
public DisplayControl DisplayPanel { get; private set; }
public InputControl InputPanel { get; private set; }
public Mediator(DisplayControl displayPanel, InputControl inputPanel)
{
Data = new Person();
DisplayPanel = displayPanel;
InputPanel = inputPanel;
//實際將InputPanel_NotifyChanged通知的function加入到Event中。
InputPanel.NotifyChanged += InputPanel_NotifyChanged;
}
private void InputPanel_NotifyChanged(object sender
, Tuple<string, string> e)
{
//實際事件通知後去更新DisplayPanel
DisplayPanel.UpdateData(e.Item1, e.Item2);
}
}
在程式碼中,InputPanel_NotifyChanged中可以塞入多個通知的對象,或是讓多個執行個體的方法去註冊InputPanel.NotifyChanged +=,也是一樣道理。
但是整個註冊過程都會透過Mediator這一個類別作統一的管理繫結的過程。就是Mediator Pattern。