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

  • 4644
  • 0
  • C#
  • 2018-12-09

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

  • OOP複習
  • Facade(門面/外觀模式)
  • Adapter(轉接器模式)
  • Singleton(單例/獨立模式)
  • Registry of Singletons(Multiton pattern)
  • SimpleFactory(簡單工廠模式)
  • Template Method(範本方法模式)
  • Chain of Responsibility(責任鏈模式)
  • State(狀態模式)

開頭先預習OOP

一個抽象(依賴抽象)

兩個目標(高內聚、低偶合)

三個特性(繼承、封裝、多型)

六個原則(單一職責、里式替換、DIP、ISP、OCP、LOD)

在開始講Design Pattern前,務必以上各項原則必須搞懂,否則很難後面靈活運用。

需求與設計原則,之間的權衡還是以需求為重。

決戰設計模式 (Design Patterns)

在專案中要融入Design Pattern是很難單一Pattern可以直接完成需求,設計過程中大概是兩個至三個Pattern融合加上變形才可能符合需求,必須確實釐清楚各個Pattern本身的特性與目的。

為了解決什麼問題而產生的Pattern或是這個Pattern解決了什麼問題。

Facade(門面/外觀模式)

目的:讓User透過高層的"夾層"來呼叫子系統,降低User對於子系統的依賴,也更輕鬆的操作複雜的子系統。

夾層的實作可以是Class也可以是Interface,主要目的必須隔離User直接操作子系統。

使用Facade的理由:

隔離User對於子系統的依賴,避免發生日後子系統修改導致User也必須跟著修改(連動性問題)。
//User程式
static void Main(string[] args)
{
    byte[] bytes = { 3, 2, 5, 4 };
    int i = MyIntConverter.ByteToInt(bytes);
}
//Facade夾層
public static int ByteToInt(byte[] b)
{
    return b[0] + b[1] * 256 + b[2] * 256 * 256 + b[3] * 256 * 256 * 256;
    //return BitConverter.ToInt32(b, 0);
}

Facade夾層,內容中的程式碼日後我可以隨意更換演算法轉換的方式,我只要保持簽章相同情況下,外部呼叫的User根本不知道內部計算方式。

這樣就可以達到子系統更改,User不必連動跟著修改的。這就是Facade的目的。

Adapter(轉接器模式)

目的:將兩個以上不同的介面統一成一個介面,讓User更輕鬆維護。

實際情境中,可能出現架構上已經設計兩套Library在專案中,突然需求需要第三個Library,這時候Adapter模式下只需要將共用介面引用至第三個Library中開發完交付給User,User只有修改new出第三套Libraray所產生的Instance就大功告成了。

//User程式
static void Main(string[] args)
{   
    //Lib_1
    ICommunication Tunnel = new UdpCommunication();
    //Lib_2
    //ICommunication Tunnel = new TcpCommunication();
    //Lib_3
    //ICommunication Tunnel = new MqttCommunication();

    Tunnel.Connect("192.167.0.100",3254);
    byte[] sendBuffer = GetSendBuffer();
    Tunnel.Send(sendBuffer);
   
    byte[] receiveBuffer = Tunnel.Receive();
    Tunnel.Disconnect();
    Console.WriteLine(GetReceiveString(receiveBuffer));

    Console.ReadLine();
}

private static byte[] GetSendBuffer()
{
    string data = "Hi Tunnel!";
    return Encoding.UTF8.GetBytes(data);
}

public static string GetReceiveString(byte[] buffer)
{
    return Encoding.UTF8.GetString(buffer);
}
/// <summary>
/// 這就是一個 Target (Adapter 的抽象)
/// </summary>
public interface ICommunication
{
    bool Connect(string targetm, int Port);

    void Disconnect();
    void Send(byte[] buffer);

    byte[] Receive();
}
public class UdpCommunication : ICommunication, IDisposable
{
    private Socket client;
    public bool Connect(string targetm, int Port)
    {
        //自行Google實做
        throw new NotImplementedException();
    }

    public void Disconnect()
    {
        //自行Google實做
        throw new NotImplementedException();
    }

    public void Dispose()
    {
        //自行Google實做
        throw new NotImplementedException();
    }

    public byte[] Receive()
    {
        //自行Google實做
        throw new NotImplementedException();
    }

    public void Send(byte[] buffer)
    {
        //自行Google實做
        throw new NotImplementedException();
    }
}

在User主程式中,Lib_1、Lib_2、Lib_3,分別三種不同的通訊方式,實際上要增加第四種方法也只要針對行產生Instance的地方做修改即可。

這樣子就可以達到User在操作Send與Receive的邏輯部分完全切割,不會因為修改不同通訊方法導致需要去修改Send與Receive的程式碼段落。

開發Adapter與Facade,都是利用夾層的概念在切割User與子系統之間的連動性,因此連動性關鍵點在於共同制定夾層的規格,每一個方法帶入參數與回傳參數等等...,都必須雙方先制定清楚再開始開發。也就變成先共同制定Interface的規格出來後,雙方人馬才會正式動工開發。

Singleton(單例/獨立模式)

目的:在程式運作中,永遠只維持一份Instance在系統中,讓所有User取得相同一個Instance。

運用情境1:在開發Socket TCP的系統中,Socket Server的連線數量對公司而言是最直接的成本因素,所以會盡量保持Client端系統永遠只保持一條連線。

運用情境2:對於DB讀取一份資料屬於長期不變動的資料,一般系統都會做Cache,減少對DB的負擔。

static void Main(string[] args)
{          
    SocketClass.SocketObject.Connect("127.0.0.1", 3254);
    byte[] sendBuffer = GetSendBuffer();
    SocketClass.SocketObject.Send(sendBuffer);

    byte[] receiveBuffer = SocketClass.SocketObject.Receive();
    SocketClass.SocketObject.Disconnect();
    Console.WriteLine(GetReceiveString(receiveBuffer));
    Console.ReadLine();
}
private static byte[] GetSendBuffer()
{
    string data = "Hi Singleton!";
    return Encoding.UTF8.GetBytes(data);
}

public static string GetReceiveString(byte[] buffer)
{
    return Encoding.UTF8.GetString(buffer);
}
public class SocketClass
{
    private Socket socket;
    /// <summary>
    /// private constructor
    /// </summary>
    private SocketClass()
    {
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream
                            , ProtocolType.Tcp);
    }
    public void Send(byte[] bytes)
    {
        socket.Send(bytes);
    }
    public bool Connect(string target, int port)
    {
        IPAddress ip;

        if (IPAddress.TryParse(target, out ip))
        {
            socket.Connect(ip, port);
        }

        return socket.Connected;
    }
    public byte[] Receive()
    {
        byte[] buffer = new byte[1024];
        int receiveSize = socket.Receive(buffer);
        Array.Resize(ref buffer, receiveSize);
        return buffer;
    }

    public void Disconnect()
    {
        socket.Close();
    }

    private static SocketClass _SocketObject;
    private static object _syncRoot = new object();
    public static SocketClass SocketObject
    {
        get
        {
            if (_SocketObject == null)
            {
                lock (_syncRoot)
                {
                    // double locking
                    if (_SocketObject == null)
                    {
                        GetSingleton();
                    }

                }
            }
            return _SocketObject;
        }
    }

    private static void GetSingleton()
    {
        _SocketObject = new SocketClass();
    }
}

Registry of Singletons(Multiton pattern)

目的:共同管理單例的Instance。

系統運作中,單例的Instance非常多,變成須要有一個共通的地方來管理會使得取出Instance更加簡便。

static void Main(string[] args)
{

    var o1 = SingletonRegistry.GetInstance<Class1>();
    var o2 = SingletonRegistry.GetInstance<Class1>();
   
    Console.WriteLine(object.ReferenceEquals(o1, o2));

    Console.ReadLine();
}
public class SingletonRegistry
{
    private static Dictionary<string, object> registry = 
                                  new Dictionary<string, object>();

    public static T GetInstance<T>() where T : class,new()
    {
        Type type = typeof(T);
        string key = type.Name;
        if (!registry.ContainsKey(key))
        {
            registry[key] = new T();
        }

        return (T)registry[key];
    }
}

SimpleFactory(簡單工廠模式)

目的:統一集中管理產生Instance的地方。

使用工廠模式可以解決分散各地產生Instance的困擾,如果分散十個地方有new Instance,異動的時候就必須十個地方都要改,如果有統一產生Instance地方那就只需要修改產生Instance的段落即可。

//User程式
static void Main(string[] args)
{
    var commucation = Factory.CommucationFactory
                        .GetInstance(Factory.CommucationType.Tcp);
    commucation.Connect("127.0.01:8888");
    Console.ReadLine();
}
//定義工廠模式
public enum CommucationType
{
    Tcp,
    Udp
}
/// <summary>
/// 使用簡單分支運算
/// </summary>
public class CommucationFactory
{
    public static ICommunication GetInstance(CommucationType type)
    {
        switch (type)
        {
            case CommucationType.Tcp:
                return new TcpCommunication();
            case CommucationType.Udp:
                return new UdpCommunication();
            default:
                throw new ArgumentOutOfRangeException();

        }
    }
}

User使用過程,產生Instance時代入參數(TCP或UDP)就可以取得相對應的Instance,後續User藉由ICommunicaton操作因此也就切開連動性,後續如果要新增加新的通訊方法也只需要加上Enum與switch case新增加方法即可。

Template Method(範本方法模式)

目的:先安排好程式固定的框架流程步驟,步驟細節交由子類別實做。

static void Main(string[] args)
{
    List<int> list = new List<int>();
    for (int i = 0; i < 10; i++)
    {
        list.Add(i);
    }
    PredicateInt o = new PredicateInt(list);
    var result = o.DoWhere();
    foreach (var item in result)
    {
        Console.WriteLine(item);
    }

    Console.ReadLine();
}
public abstract  class CustomClass<T>
{
    private IEnumerable<T> _source;

    public CustomClass(IEnumerable<T> source)
    {
        _source = source;
    }

    public IEnumerable<T> DoWhere()
    {
        foreach (var item in _source)
        {
            if (Predicate(item))
            {
                yield return item;
            }
        }
    }
    protected abstract bool Predicate(T item);
}
class PredicateInt : CustomClass<int>
{
    public PredicateInt(IEnumerable<int> source) : base(source)
    {
    }

    protected override bool Predicate(int item)
    {
        return item > 5;
    }
}

在Main程式中 var result = o.DoWhere();,中的DoWhere()會跑到CustomClass中去執行foreach,在foreach中的Predicate中的Method的部分就是交由User自行定義。

在ProedicateInt類別中Predicate這個Method定義了判斷是item > 5,那回歸到CustomClass中在判斷if(Predicate(item))時就是item>5。

這樣就可以達到說在DoWhere的框架下必定會跑一個foreach的迴圈,裡面固定會判斷if(Predicate(item))的既定流程,但是Predicate的判斷條件又是交給User自行設計,就可以達到tempate的效果了。

Chain of Responsibility(責任鏈模式)

目的:透過物件的方式去取代if波動拳的撰寫方式。

運用情境:需求是必須經過層層把關而且必定是依序檢查不得跳躍的情境下就是適合的情境。

static void Main(string[] args)
{
    var checker = ChainContext.GetCheckers();
    List<CheckResult> results = new List<CheckResult>();
    foreach (var item in FakeDataSource.Data)
    {
        results.Add(checker.Check(item));
    }

    foreach (var item in results)
    {
        Console.WriteLine($"Source : {item.Source } , Result ={item.Result}");
    }
    Console.ReadLine();
}

 

public class CheckResult
{
    public string Source { get; set; }
    public bool Result { get; set; }
}

public abstract class FormatChecker
{
    protected FormatChecker _successor;
    protected abstract bool InternalCheck(string source);
    public CheckResult Check(string source)
    {
        // 檢查結果為 true, 若沒有後繼者,表示檢查結束, 若有後繼者則繼續往下處理
        // 檢查結果為 false, 則跳出, 不再處理
        if (InternalCheck(source))
        {
            if (_successor != null)
            {
                return _successor.Check(source);
            }
            else
            {
                return new CheckResult() { Source = source, Result = true };
            }
        }
        else
        {
            return new CheckResult() { Source = source, Result = false };
        }
    }

    protected FormatChecker(FormatChecker successor)
    {
        _successor = successor;
    }
}

/// <summary>
/// 長度檢查
/// </summary>
internal class LengthChecker : FormatChecker
{
    public LengthChecker(FormatChecker successor) : base(successor)
    {
    }

    protected override bool InternalCheck(string source)
    {
        return source.Length == 29;
    }
}

/// <summary>
/// 開頭檢查
/// </summary>
internal class HeadChecker : FormatChecker
{

    public HeadChecker(FormatChecker successor) : base(successor)
    {

    }

    protected override bool InternalCheck(string source)
    {

        string head = source.Substring(0, 3);
        return int.TryParse(head, out int x);

    }
}

/// <summary>
/// 第一個日期檢查
/// </summary>
internal class FirstDateChecker : FormatChecker
{
    public FirstDateChecker(FormatChecker successor) : base(successor)
    {
    }

    protected override bool InternalCheck(string source)
    {
        var dateString = source.Substring(13, 8);

        return double.TryParse(dateString, out double x);
    }
}

/// <summary>
/// 第二個日期檢查
/// </summary>
internal class SecondDateChecker : FormatChecker
{
    public SecondDateChecker(FormatChecker successor) : base(successor)
    {
    }

    protected override bool InternalCheck(string source)
    {
        var dateString = source.Substring(21, 8);

        return double.TryParse(dateString, out double x);
    }
}
public class ChainContext
{
    public static FormatChecker GetCheckers()
    {
        return new LengthChecker(new HeadChecker(new FirstDateChecker(new SecondDateChecker(null))));
    }
}

我們先從ChainContext類別講起,看到return的地方連續new多個類別一層又一層的帶入,這樣就有責任鏈的感覺了,就像鍊子一樣環環相扣。

在ChainContext中new的起始點就是SecondDateChecker(null)開始產生Instance,從建構式中可以看到有:base(successor)的繼承,意思就是會將successor的值往父類別拋,就是abstract class FormatChecker這一個類別,在FormatChecker的建構式相當單純只是將代入的值存起來而已。執行完_successor = successor;就相當於SecondDateChecker類別的執行個體算產生完畢,這一個SecondDateChecker的Instance會繼續往下傳到FirstDateChecker類別的建構式之中,再重複剛剛的動作一次,後續以此類推。

回到Main主程式中,執行完 var checker = ChainContext.GetCheckers();產生所有類別的Instance之後繼續Main程式中執行results.Add(checker.Check(item));的Check(item)動作,對應到FormatChecker類別中的Check(string source)方法,其中if (InternalCheck(source))的InternalCheck這一個方法必須參考到當下的Instance的類別是哪一個,第一次進入就是最外層的LengthChecker這一個類別的Instance,LengthChecker類別中override的InternalCheck方法內部判斷是return source.Length == 29;,執行判斷完後就會回傳至FormatChecker類別中,判斷True,就會交棒給下一個_successor這一個Instance,繼續剛剛的重複動作直到_successor==null才會停止或是其中一個Instance判斷是False才會跳離開責任鏈。

State(狀態模式)

目的:利用抽象型別抽出共用的方法,依照子類別或是委派方式塞入實際執行細節,抽象層依照不同的子類別實作方法或是委派方法去執行。(很像template模式)

static void Main(string[] args)
{
    StrategyContext context = new StrategyContext();
    context.ExecuteStrategy("Strategy2");
    Console.ReadLine();
}
public abstract class AbstractStrategy
{
    public abstract void AlgorithmMethod();
}

internal class Strategy1 : AbstractStrategy
{

    public override void AlgorithmMethod()
    {
        Console.WriteLine("Strategy 1");
    }
}

internal class Strategy2 : AbstractStrategy
{

    public override void AlgorithmMethod()
    {
        Console.WriteLine("Strategy 2");
    }
}
public class StrategyContext
{
    public void ExecuteStrategy(string args)
    {
        var strategy = StrategyFactory.CreateStrategy(args);
        if (strategy != null)
        {
            strategy.AlgorithmMethod();
        }
    }
}

internal class StrategyFactory
{
    public static AbstractStrategy CreateStrategy(string args)
    {
        switch (args)
        {
            case "Strategy1":
                return new Strategy1();
            case "Strategy2":
                return new Strategy2();
            default:
                return null;
        }

    }
}

程式碼講解:

主要目的是要執行StrategyContext類別當中的strategy.AlgorithmMethod();這是定義共用的執行方法,藉由StrategyFactory來去回傳實際產生的Instance,不同的Instance的內容AlgorithmMethod()的實作方法都不同。

對於User而言使用只需要決定只要帶入switch case的值就可以任意切換到對應的Instance來執行方法。