# 13. Proxy ###### tags: `DesignPatterns` ## 關於 Proxy 本篇將討論以下幾個問題 > ### 1. 關於 Proxy > ### 2. UML > ### 3. 將 UML 轉為程式碼 > ### 4. 情境 --- ## 測試環境: >OS:Windows 10 >IDE:Visual Studio 2019 --- ## 1. 關於 Proxy > Provide a surrogate or placeholder for another object to control access to it. > > by [Gang of Four](https://en.wikipedia.org/wiki/Design_Patterns) - 為一對象(原實作)提供代理或替代,以控制對其(原實作)的訪問 Proxy(代理)屬於結構型(Structural Patterns),當遇到對象建立較**耗費資源**時,可使用 Proxy 來延遲建立,當真正需要使用到這個物件時才會建立,而非先建立好等待使用。當遇到**控管或隔離**的需求時,可使用 Proxy 來管理是否能夠取得被控管對象。 優點: - 符合 開閉原則(Open Closed Principle) 缺點: - 並非所有呼叫端都透過 Proxy 呼叫,程式複雜度增加 --- ## 2. UML ![](https://i.imgur.com/cOTxBLQ.jpg) Class 間關聯: - Client 關聯 Subject - RealSubject & Proxy 繼承 Subject - Proxy 關聯 RealSubject Class: - Client:呼叫端 - Subject:被代理之服務的抽象類別或介面 - RealSubject:被代理之服務 - Proxy:代理 --- ## 3. 將 UML 轉為程式碼 被代理之服務介面 ```C# /// <summary> /// 被代理之服務介面 /// </summary> public interface ISubject { void Request(); } ``` 被代理之服務 ```C# /// <summary> /// 被代理之服務 /// </summary> public class RealSubject : ISubject { public void Request() { Console.WriteLine("Called RealSubject.Request()"); } } ``` 代理,繼承被代理之服務介面 ```C# /// <summary> /// 代理,繼承被代理之服務介面 /// </summary> public class Proxy : ISubject { private RealSubject _realSubject; public void Request() { // Use 'lazy initialization' if (_realSubject == null) { _realSubject = new RealSubject(); } Console.WriteLine("Called Proxy.Request()"); _realSubject.Request(); } } ``` 1. 建立代理`proxy` 2. 透過代理呼叫被代理物件 ```C# static void Main(string[] args) { Default.Proxy proxy = new Default.Proxy(); proxy.Request(); Console.ReadLine(); } ``` 執行結果 ```Console Called Proxy.Request() Called RealSubject.Request() ``` --- ## 4. 情境 我們接到了一個銷售報表 API 加上權限的需求 - 角色分為店長 & 店員 - 店長可以看到銷售報表,店員無法 定義角色 ```C# /// <summary> /// 角色 /// </summary> public enum Position { StoreManager, Clerk } ``` 銷售報表介面 ```C# /// <summary> /// 銷售報表介面 /// </summary> public interface IRevenueReport { void GetReport(); } ``` 銷售報表實作 ```C# /// <summary> /// 銷售報表實作 /// </summary> public class RevenueReport : IRevenueReport { public void GetReport() { Console.WriteLine("Return revenue report."); } } ``` 代理,繼承銷售報表介面 ```C# /// <summary> /// 代理,繼承銷售報表介面 /// </summary> public class Proxy : IRevenueReport { private RevenueReport _revenueReport { get; set; } private Position _position { get; } public Proxy(Position position) { _position = position; Console.WriteLine($"Position:{_position.ToString()}"); } public void GetReport() { if (_position != Position.StoreManager) { Console.WriteLine($"{_position.ToString()} can't get revenue report."); return; } // Use 'lazy initialization' if (_revenueReport == null) { _revenueReport = new RevenueReport(); } _revenueReport.GetReport(); } } ``` 1. 建立代理,分別使用店長 & 店員角色 2. 透過代理呼叫取得報表 ```C# static void Main(string[] args) { Situation.Proxy proxyStoreManager = new Situation.Proxy(Situation.Position.StoreManager); proxyStoreManager.GetReport(); Console.WriteLine($"\n"); Situation.Proxy proxyClerk = new Situation.Proxy(Situation.Position.Clerk); proxyClerk.GetReport(); Console.ReadLine(); } ``` 執行結果 ```Console Position:StoreManager Return revenue report. Position:Clerk Clerk can't get revenue report. ``` --- ## 完整程式碼 GitHub:[Structural_07_Proxy](https://github.com/darionnnnnn/blog/tree/master/Blog/Structural_07_Proxy) --- ## 總結 ### Proxy 的目的並非加強原實作的功能,而是附加上與原實作邏輯無關的操作(e.g. 權限控管、log、延遲建立等),而 Proxy 在使用時盡量不要加入需要等待的呼叫,避免因此而造成服務延遲。 --- ## 參考資料 1. [Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) 2. [大話設計模式](https://www.tenlong.com.tw/products/9789866761799) 2. [dofactory](https://www.dofactory.com/) 3. [Refactoring.Guru](https://refactoring.guru/) --- ## 新手上路,若有錯誤還請告知,謝謝