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

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

  • Prototype(原型模式)
  • Memento(備忘錄模式)
  • Decorator(裝飾模式)
  • Proxy(代理模式)
  • Builder(建造者模式)

Prototype(原型模式)

目的:作物件複製。

物件複製分成兩種,淺層複製與深層複製。

淺層複製:對於物件本身的實值型別(int、string、double....)做複製,參考型別『沒有』複製。複製後的物件內的參考型別欄位會與原先物件共用相同物件。
深層複製:完整的複製整個物件,參考型別與實值型都完整複製,與原先物件完全分離。 

 做複製物件的方式,可以藉由Net Framework的ICloneable Interface來繼承,實做Clone()的方法,實做clone方法內使用MemberwiseClone()方法來達到淺層複製。

public class Class1 : ICloneable
{
    public int X { get; set; }
    public int Y { get; set; }
    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

public class Class2 : ICloneable
{
    public Class1 Data { get; set; }
    public string Id { get; set; }

    public Class2()
    {
        Data = new Class1();
    }

    public object Clone()
    {
        var result = (Class2)this.MemberwiseClone();

        if (this.Data != null)
        {
            //深層複製
            result.Data = (Class1)this.Data.Clone();
        }

        return result;
    }
}
做深層複製的過程務必確實,若有遺漏參考型別欄位沒有實作深層複製,會導致複製後的物件與原先物件會參考相同子物件。

 Memento(備忘錄模式)

目的:完整複製物件內的狀態,儲存至其他地方,達到復原機制。

使用情境,例如上一頁或上一步的功能,或是當前使用畫面的布局與資料儲存當下狀態,下次重新啟動程式可以直接恢復至上一次關閉的狀態。

//幫忙保管備份的執行個體,絕不讓其他人修改
//跟海賊王的推進城麥哲倫差不多意思
static MementoCaretaker _caretaker;

//備份的主要目標就是該Model
static Model _model;
static void Main(string[] args)
{
    _model = new Model();

    _model.class1.x = 10;
    _model.class1.class2.a = 5;

    //執行備份
    CreateCaretaker();
    
    Console.WriteLine("class1.x:" + _model.class1.x);
    Console.WriteLine("class1.class2.a:" + _model.class1.class2.a);

    _model.class1.x = 2;
    _model.class1.class2.a = 0;

    Console.WriteLine("class1.x:" + _model.class1.x);
    Console.WriteLine("class1.class2.a:" + _model.class1.class2.a);

    //執行還原
    _model.SetMemento(_caretaker.Memento);

    Console.WriteLine("class1.x:" + _model.class1.x);
    Console.WriteLine("class1.class2.a:" + _model.class1.class2.a);
    _caretaker = null;
    Console.ReadLine();
}

private static void CreateCaretaker()
{
    //先把保管者先產生出執行個體,產生麥!哲!倫!...毒!毒!毒!蚯蚓....來也!!
    _caretaker = new MementoCaretaker();
    //對需要保管的東西做一個複製的動作(由請獵人的庫嗶登場!!登登登~!!!)
    _caretaker.Memento = _model.CreateMemento();//執行複製
    //庫嗶施展具現化系惡魔的左右手複製出東西,然後放進麥哲倫的口袋保管
}
//麥哲倫本人
public class MementoCaretaker
{
    private ModelMemento _memento;
    public ModelMemento Memento
    {
        get
        {
            return _memento;
        }
        set
        {
            _memento = value;
        }
    }
} 
class Model
{
    public Class1 class1;
    public Class3 class3;
    public Model()
    {
        class1 = new Class1();
        class3 = new Class3();
    }

    //執行複製的方法,想像就像拓印一樣,然後return出去把副本交給麥哲倫保管
    public ModelMemento CreateMemento()
    {
        //實際需要複製的欄位有 class1,class3
        return new ModelMemento(class1, class3);
    }
    //執行拿出備份(memento)做還原的方法
    public void SetMemento(ModelMemento memento)
    {
        class1 = (Class1)memento.class1.Clone();
        class3 = (Class3)memento.class3.Clone();
    }
}
/// <summary>
/// 備份檔
/// </summary>
/// 其實ModelMemento就是一張複寫紙,針對Model裡面需要複製的欄位填入ModelMemento
/// 就是代表希望儲存的欄位有哪些,未來做還原的執行個體
public class ModelMemento
{
    public Class1 class1 { get; private set; }
    public Class3 class3 { get; private set; }

    //庫嗶正在執行具現化系的招數,惡魔的左右手~!!
    public ModelMemento(Class1 _class1, Class3 _class3)
    {
        class1 = (Class1)_class1.Clone();
        class3 = (Class3)_class3.Clone();
    }
}
public class Class1 : ICloneable
{
    public int x { get; set; }
    public int y { get; set; }
    public Class2 class2 { get; set; }
    public Class1()
    {
        class2 = new Class2();
    }
    public object Clone()
    {
        var obj = (Class1)this.MemberwiseClone();
        if (class2 != null)
        {
            //實做深層複製,就等於new新的執行個體,然後把值塞進去。
            obj.class2 = (Class2)this.class2.Clone();
        }
        return obj;
    }
}

public class Class2 : ICloneable
{
    public int a { get; set; }
    public int b { get; set; }

    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

public class Class3 : ICloneable
{
    public int k { get; set; }
    public int j { get; set; }
    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

循著程式碼的脈絡,從Main主程式開始,一開頭就先執行CreateCaretaker();方法將原先的執行個體複製一份出來儲存,儲存在_caretaker。

經過修改值之後,執行還原動作SetMemento(_caretaker.Memento),將原先儲存的執行個體的參數在復原回去。

Decorator(裝飾模式)

目的:將原本舊有的類別新增新的職責。

裝飾模式我們可以先講一段故事,今天一條做壓模的生產線,初始設計會是輸送帶與壓模器這兩樣製作過程是一體成型的,日後老闆說我們今天壓模的圖案要改,這時候工程師就要去製造新的一條輸送帶與壓模器,此時就擁有兩條輸送帶。

裝飾模式就如你所想,當初製作壓模器怎麼沒有把模具可以抽換的概念設計進去呢,此時抽換模具的這一個功能就是裝飾模式,讓你可以抽換壓模的圖案,不需要建造新的輸送帶,輸送帶就是你的程式主流程。

/// <summary>
/// Decorator 的抽象(建立輸送帶)
/// </summary>
public abstract class FileDecorator : IFileProcess
{
    //模具的抽屜軌道,讓你可以抽換的滑軌,等具體的模組插進來,先留好洞洞
    protected readonly IFileProcess _fileProcess;
    //利用建構式,執行插入模具動作
    protected FileDecorator(IFileProcess fileProcess)
    {
        _fileProcess = fileProcess;
    }
    public abstract byte[] Read(string path);
    //壓模器的動作
    public abstract void Write(string path, byte[] data);
}
/// <summary>
/// Base64 裝飾器 (實做賓士圖案模具)
/// </summary>
public class Base64FileDecorator : FileDecorator
{
    public Base64FileDecorator(IFileProcess fileProcess) : base(fileProcess)
    {
    }

    public override byte[] Read(string path)
    {
        var base64Bytes = _fileProcess.Read(path);
        return Decode(base64Bytes);
    }

    private byte[] Decode(byte[] base64Bytes)
    {
        var bytes = Convert.FromBase64String(Encoding.UTF8.GetString(base64Bytes));
        return bytes;
    }

    public override void Write(string path, byte[] data)
    {
        //執行壓模Encode(代表賓士的圖案)
        var base64Bytes = Encode(data);
        //存檔(包裝進倉儲給他丟進倉庫)
        _fileProcess.Write(path, base64Bytes);
    }

    private byte[] Encode(byte[] data)
    {
        return Encoding.UTF8.GetBytes(Convert.ToBase64String(data));
    }
}

/// <summary>
/// Gzip 壓縮裝飾器 (實做BMW圖案模具)
/// </summary>
public class GZipFileDecorator : FileDecorator
{
    public GZipFileDecorator(IFileProcess fileProcess) : base(fileProcess)
    {

    }

    public override byte[] Read(string path)
    {
        byte[] compressedBytes = _fileProcess.Read(path);
        return Decompress(compressedBytes);
    }

    private byte[] Decompress(byte[] compressedBytes)
    {
        byte[] outputBytes = null;
        MemoryStream input = new MemoryStream(compressedBytes);
        outputBytes = Decompress(input).ToArray();

        return outputBytes;
    }
    private MemoryStream Decompress(Stream compressed)
    {
        var decompressed = new MemoryStream();
        using (var zip = new GZipStream(compressed, CompressionMode.Decompress, true))
        {
            zip.CopyTo(decompressed);
        }
        decompressed.Seek(0, SeekOrigin.Begin);
        return decompressed;
    }

    public override void Write(string path, byte[] data)
    {
        //執行壓模Compress(代表BMW的圖案)
        byte[] outputBytes = Compress(data);
        //存檔(包裝進倉儲給他丟進倉庫)
        _fileProcess.Write(path, outputBytes);
    }

    private byte[] Compress(byte[] data)
    {
        byte[] outputBytes = null;
        MemoryStream input = new MemoryStream(data);
        outputBytes = Compress(input).ToArray();
        return outputBytes;
    }
    private MemoryStream Compress(Stream decompressed)
    {
        var compressed = new MemoryStream();
        using (var zip = new GZipStream(compressed, CompressionLevel.Fastest, true))
        {
            decompressed.CopyTo(zip);
        }
        compressed.Seek(0, SeekOrigin.Begin);
        return compressed;
    }
}
//生產線輸送帶
static void Main(string[] args)
{
    string path = ".";
    //壓模的原物料就是方塊的一塊鐵片
    var writebytes = Encoding.UTF8.GetBytes("123");
    //實際產生賓士的Logo模具,FileProcess代表包裝的禮盒                     
    var writeDecorator = new Base64FileDecorator(new FileProcess());
    //實際產生BMW的Logo模具,FileProcess代表包裝的禮盒
    //var writeDecorator = new GZipFileDecorator(new FileProcess());

    //對鐵片執行壓模動作
    writeDecorator.Write(path, writebytes);
}

由主程式代表一條生產線的所有流程,從丟入原物料到壓模到包裝。

Base64FileDecorator與GZipFileDecorator代表實際產生的模具,決定了壓模的圖案,最後FileProcess代表包裝的禮盒,可以任意抽換禮盒,賓士跟BMW的禮盒不可能一樣吧!!

Proxy(代理模式)

目的:為其他物件提供一種代理,藉由這種方式控制對該物件的存取。

又是另一段故事的開始,某年某月一名不知名的『男子』萌生起想娶越南妹的念頭(不是我、不是我、不是我),此時恨自己不會講越南話只好找上『仲介』,透過仲介來去娶『越南妹』,相信要娶到優質的越南妹所開出來的條件應該...。

//仲介
public class FileProcessProxy : IFileProcess
{
    //越南妹
    private IFileProcess _subject;
    //男子
    private User _user;

    //男子透過建構式找上仲介
    public FileProcessProxy(User user)
    {
        _user = user;
        //仲介突然get越南妹
        _subject = new FileProcessAdapter();
    }

    public byte[] Read(string path)
    {
        if (CanRead())
        {
            return _subject.Read(path);
        }
        else
        {
            throw new UnauthorizedAccessException("使用者沒有讀取權限");
        }
    }

    public void Write(string path, byte[] data)
    {
        //問是否符合越南妹的條件
        if (CanWrite())
        {
            //通過條件,娶回家
            _subject.Write(path, data);
        }
        else
        {
            //沒通過,一輩子單身
            throw new UnauthorizedAccessException("使用者沒有寫入權限");
        }
    }

    private bool CanRead()
    {
        return ((_user.FileAuthority & Authority.Read) == Authority.Read);
    }

    private bool CanWrite()
    {
        //越南妹提出的條件!!
        return ((_user.FileAuthority & Authority.Write) == Authority.Write);
    }
}
//故事是這樣開始的
static void Main(string[] args)
{
    string path = ".";

    //實際產生一名男子
    User user = new User();
    //男子身材實際具有的身材表現擁有30CM的長度191的身高
    user.FileAuthority = (Authority.Write | Authority.Read);
    //實際找上仲介
    FileProcessProxy writeProxy = new FileProcessProxy(user);
    //執行娶越南妹
    writeProxy.Write(path, Encoding.UTF8.GetBytes("123"));
}

Proxy模式循著主程式實際走一遍應該不難懂,主要是男子要執行娶越南妹,都會經過仲介然而執行審核,利用這樣的方式來限制存取權限。

Builder(建造者模式)

目的:將一個複雜物件的建構與它的表示分離,使得同樣的建構過程可以創建出不同的配置或佈局。

今天又是下雨的一天特別適合組鋼彈模型,買了兩盒的鋼彈模型打開第一步就是先看『組裝順序說明書』與『零件說明圖規格書』與『實體零件』

//零件說明圖與規格書,說明盒內具有哪些零件各幾個
public abstract class FileProcessBuilder
{
    protected IFileProcess _fileProcess;

    protected FileProcessBuilder()
    {
        _fileProcess = new FileProcess();
    }

    public abstract void BuildEncode();
    public abstract void BuildCrypto();
    public abstract void BuildCompress();

    public IFileProcess GetFileProcess()
    {
        return _fileProcess;
    }
}

//實體零件的產生動作,就是把它從零件架上剪下來的動作
public class DESBuilder : FileProcessBuilder
{
    public override void BuildCompress()
    {
        _fileProcess = new GZipFileDecorator(_fileProcess);
    }

    public override void BuildCrypto()
    {
        _fileProcess = new DESCryptoFileDecorator(_fileProcess);
    }

    public override void BuildEncode()
    {
        _fileProcess = new Base64FileDecorator(_fileProcess);
    }
}
 //零件組裝順序說明書
public class FileProcessDirector
{
    public FileProcessDirector(FileProcessBuilder builder)
    {
        //執行組裝
        builder.BuildEncode();//步驟1.
        builder.BuildCompress();//步驟2.
        builder.BuildCrypto();//步驟3.
    }
}
static void Main(string[] args)
{
    string path = "1.txt";
    if (File.Exists(path))
    {
        File.Delete(path);
    }
    string newline = Environment.NewLine;
    string expected = "測試 文字" + newline + "GZip" + newline + "加密" + newline + "Base64" + newline + "檔案存取";
    var writebytes = Encoding.UTF8.GetBytes(expected);

    FileProcessBuilder writeBuilder = new DESBuilder();//DESBuilder代表第一盒鋼彈
    //FileProcessBuilder writeBuilder = new XXXBuilder();//XXXBuilder代表第二盒鋼彈
    //FileProcessDirector代表零件組裝順序說明書
    FileProcessDirector writeDirector = new FileProcessDirector(writeBuilder);

    IFileProcess writeProcess = writeBuilder.GetFileProcess();

    Console.ReadLine();
}

在Main主程式中FileProcessDirector扮演著組裝順序的說明書,事實上可能第一盒鋼彈與第二盒鋼彈的組裝順序不同,我們會寫新FileProcessDirector類別來去符合第二盒鋼彈的組裝順序。

在new FileProcessDirector完畢後,就是已經得到一台實體的鋼彈了。