13. Proxy


Posted by WayneCheng on 2021-01-20

關於 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

  • 為一對象(原實作)提供代理或替代,以控制對其(原實作)的訪問

Proxy(代理)屬於結構型(Structural Patterns),當遇到對象建立較耗費資源時,可使用 Proxy 來延遲建立,當真正需要使用到這個物件時才會建立,而非先建立好等待使用。當遇到控管或隔離的需求時,可使用 Proxy 來管理是否能夠取得被控管對象。

優點:

  • 符合 開閉原則(Open Closed Principle)

缺點:

  • 並非所有呼叫端都透過 Proxy 呼叫,程式複雜度增加

2. UML

Class 間關聯:

  • Client 關聯 Subject
  • RealSubject & Proxy 繼承 Subject
  • Proxy 關聯 RealSubject

Class:

  • Client:呼叫端
  • Subject:被代理之服務的抽象類別或介面
  • RealSubject:被代理之服務
  • Proxy:代理

3. 將 UML 轉為程式碼

被代理之服務介面

/// <summary>
/// 被代理之服務介面
/// </summary>
public interface ISubject
{
    void Request();
}

被代理之服務

/// <summary>
/// 被代理之服務
/// </summary>
public class RealSubject : ISubject
{
    public void Request()
    {
        Console.WriteLine("Called RealSubject.Request()");
    }
}

代理,繼承被代理之服務介面

/// <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. 透過代理呼叫被代理物件
static void Main(string[] args)
{
    Default.Proxy proxy = new Default.Proxy();
    proxy.Request();

    Console.ReadLine();
}

執行結果

Called Proxy.Request()
Called RealSubject.Request()

4. 情境

我們接到了一個銷售報表 API 加上權限的需求

  • 角色分為店長 & 店員
  • 店長可以看到銷售報表,店員無法

定義角色

/// <summary>
/// 角色
/// </summary>
public enum Position
{
    StoreManager,
    Clerk
}

銷售報表介面

/// <summary>
/// 銷售報表介面
/// </summary>
public interface IRevenueReport
{
    void GetReport();
}

銷售報表實作

/// <summary>
/// 銷售報表實作
/// </summary>
public class RevenueReport : IRevenueReport
{
    public void GetReport()
    {
        Console.WriteLine("Return revenue report.");
    }
}

代理,繼承銷售報表介面

/// <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. 透過代理呼叫取得報表
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();
}

執行結果

Position:StoreManager
Return revenue report.

Position:Clerk
Clerk can't get revenue report.

完整程式碼

GitHub:Structural_07_Proxy


總結

Proxy 的目的並非加強原實作的功能,而是附加上與原實作邏輯無關的操作(e.g. 權限控管、log、延遲建立等),而 Proxy 在使用時盡量不要加入需要等待的呼叫,避免因此而造成服務延遲。


參考資料

  1. Design Patterns
  2. 大話設計模式
  3. dofactory
  4. Refactoring.Guru

新手上路,若有錯誤還請告知,謝謝


#designpattern #CSharp







Related Posts

C# 動態生成物件與其他物件綁定

C# 動態生成物件與其他物件綁定

依賴注入與自動測試

依賴注入與自動測試

CH 10 函式(function)

CH 10 函式(function)


Comments