決戰設計模式 第三天筆記(Design Patterns)

決戰設計模式 第三天筆記(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。