決戰設計模式 第二天筆記(Design Patterns)
- Prototype(原型模式)
- Memento(備忘錄模式)
- Decorator(裝飾模式)
- Proxy(代理模式)
- Builder(建造者模式)
Prototype(原型模式)
目的:作物件複製。
物件複製分成兩種,淺層複製與深層複製。
做複製物件的方式,可以藉由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完畢後,就是已經得到一台實體的鋼彈了。