裝飾者模式是一個很精美且優雅的模式
本篇範例 文字內容->AES加密->Zip檔附加密碼->輸出儲存
情境 有個需求要做
可發現需求一直在對於文字內容 操作順序做變化,但他們核心離不開對於文字內容 的操作
這種情境很適合來使用 [裝飾者模式]
裝飾者模式 有兩個主要角色 被裝飾物件(Decorated) & 裝飾物件(Decorator)
被裝飾物件(Decorated)就像蛋糕的一樣,
裝飾物件(Decorator)就是上的水果,奶油,巧克力…等等裝飾物品
一般先有蛋糕被裝飾物件(Decorated) ,後再將裝飾物品加上去裝飾物件(Decorator)
被裝飾物件(Decorated)如下圖 蛋糕的原型
圖片來源
將物件有效的往上附加職責,不動到內部的程式碼, 在原來職責上附加額外的職責
裝飾者模式運作就像 俄羅斯娃娃一樣 一層包一層
圖片來源
第一步 先找尋他們共同裝飾東西,因為是讀寫檔案 所以我們可以對於Byte 下手
先做出 讀 跟 寫 介面簽章當作裝飾動作的統一介面
1 2 3 4 5 6 public interface IProcess { byte [] Read (string path ) ; void Write (string writePath, byte [] buffer ) ; }
在創建一個 ProcessBase 給日後裝飾物品(Decorator)來繼承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public abstract class ProcessBase : IProcess { protected IProcess _process; public abstract byte [] Read (string path ) ; public abstract void Write (string writePath, byte [] buffer ) ; public virtual void SetDecorated (IProcess process ) { _process = process; } }
有兩點特別說明
protected IProcess _process;
儲存被裝飾的物件由 SetDecorated
方法來設置被裝飾的物件
就像俄羅斯娃娃只包裹一個娃娃,不管被包裹娃娃之前包含哪些娃娃
第二步 創建被裝飾物品(Decorated)
因為是檔案我們直接使用
File.ReadAllBytes 讀 檔案
File.WriteAllBytes 寫 檔案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class FileProcess : IProcess { public byte [] Read (string path ) { return File.ReadAllBytes(path); } public void Write (string writePath, byte [] buffer ) { File.WriteAllBytes(writePath, buffer); } }
第三步 創建裝飾物品(Decorator)
這次主要裝飾物品有兩個
加壓解壓ZIP檔
加解密
加密裝飾器繼承ProcessBase
並按照加解密重寫 Write
和 read
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public class AESCrypProcess : ProcessBase { private AesCryptoServiceProvider aes; public string AESKey { get ; set ; } = "1776D8E110124E75" ; public string AESIV { get ; set ; } = "B890E7F6BA01C273" ; public AESCrypProcess () { aes = new AesCryptoServiceProvider(); aes.Key = Encoding.UTF8.GetBytes(AESKey); aes.IV = Encoding.UTF8.GetBytes(AESIV); } public override byte [] Read (string path ) { byte [] encryptBytes = _process.Read(path); return DecryptData(encryptBytes); } private byte [] DecryptData (byte [] encryptBytes ) { byte [] outputBytes = null ; using (MemoryStream memoryStream = new MemoryStream(encryptBytes)) { using (CryptoStream decryptStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Read)) { MemoryStream outputStream = new MemoryStream(); decryptStream.CopyTo(outputStream); outputBytes = outputStream.ToArray(); } } return outputBytes; } public override void Write (string path, byte [] data ) { byte [] outputBytes = EncryptData(data); _process.Write(path, outputBytes); } private byte [] EncryptData (byte [] data ) { byte [] outputBytes = null ; using (MemoryStream memoryStream = new MemoryStream()) { using (CryptoStream encryptStream = new CryptoStream(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write)) { MemoryStream inputStream = new MemoryStream(data); inputStream.CopyTo(encryptStream); encryptStream.FlushFinalBlock(); outputBytes = memoryStream.ToArray(); } } return outputBytes; } }
這次讀寫zip使用 SharpZipLib 開源第三方插件
ZIP裝飾器繼承ProcessBase
並按照加解密重寫 Write
和 read
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class ZipProcess : ProcessBase { public string PassWord { get ; set ; } public string FileName { get ; set ; } public override byte [] Read (string path ) { byte [] buffer = _process.Read(path); return ZipReader(path, buffer); } public override void Write (string writePath, byte [] data ) { byte [] buffer = ZipWriter(data); _process.Write(writePath, buffer); } private byte [] ZipWriter (byte [] buffer ) { using (MemoryStream outputMemStream = new MemoryStream()) using (ZipOutputStream zipStream = new ZipOutputStream(outputMemStream)) using (MemoryStream memStreamIn = new MemoryStream(buffer)) { zipStream.SetLevel(9 ); ZipEntry newEntry = new ZipEntry(FileName); newEntry.DateTime = DateTime.Now; zipStream.Password = PassWord; zipStream.PutNextEntry(newEntry); StreamUtils.Copy(memStreamIn, zipStream, new byte [4096 ]); zipStream.CloseEntry(); zipStream.IsStreamOwner = false ; zipStream.Close(); return outputMemStream.ToArray(); } } private byte [] ZipReader (string filePath, byte [] buffer ) { byte [] zipBuffer = default (byte []); using (MemoryStream memoryStream = new MemoryStream(buffer)) { memoryStream.Seek(0 , SeekOrigin.Begin); var zip = new ZipFile(memoryStream); zip.Password = PassWord; using (MemoryStream streamWriter = new MemoryStream()) { byte [] bufferReader = new byte [4096 ]; var file = zip.GetEntry(FileName); if (file != null ) { var zipStream = zip.GetInputStream(file ); StreamUtils.Copy(zipStream, streamWriter, bufferReader); zipBuffer = streamWriter.ToArray(); } } } return zipBuffer; } }
上面就把我們要用的裝飾物品 (備料) 準備完成
第四步 創建使用(開始擺盤)
創建一個 DecorateFactory
來當生產 裝飾產品的工廠
建構子傳入一個 被裝飾的物件(FileProcess
) 之後可依照喜好一直疊加 裝飾物品(ZipProcess
,AESCrypProcess
…)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class DecorateFactory { IProcess _original; public DecorateFactory (IProcess original ) { _original = original; } public DecorateFactory SetProcess (ProcessBase process ) { process.SetDecorated(_original); _original = process; return this ; } public IProcess GetProcess () { return _original; } }
裝飾者模式 裝飾的順序是很重要的
為了方便讀者閱讀 我使用小畫家畫出 讀寫順序
如下圖
使用就可很清晰來用
DecorateFactory
來創建裝飾流程factroy.GetProcess();
方法取得完成後的產品
在簡單呼叫讀 和寫 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 string filePath = @"C:\Users\daniel.shih\Desktop\test.zip" ;string content = $"你好 123456 12@()!@ {Environment.NewLine} fsfd嘻嘻哈哈!!" ;DecorateFactory factroy = new DecorateFactory(new FileProcess()); factroy.SetProcess(new AESCrypProcess()) .SetProcess(new ZipProcess() { FileName = "1.txt" ,PassWord ="1234567" }); IProcess process = factroy.GetProcess(); byte [] data_buffer = Encoding.UTF8.GetBytes(content);process.Write(filePath, data_buffer); byte [] buffer = process.Read(filePath);Console.WriteLine(Encoding.UTF8.GetString(buffer));
日後不管需求是改成
文字內容->壓縮zip(附上密碼)->輸出儲存
文字內容->AES加密->輸出儲存
文字內容->AES加密->Zip檔附加密碼->輸出儲存
還是…..
我們都不怕因為我們把各種操作封裝
和多態
各個模組間都是獨立的很好映證 高內聚低耦合
的設計原則
小結:
裝飾者模式是一個很精美且優雅的模式 希望這篇文章可讓讀者對於此模式有更加了解
GitHub範例連結
__此文作者__:Daniel Shih(石頭) __此文地址__: https://isdaniel.github.io/decorator-pattern/ __版權聲明__:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!