代理デザインパターン:構造、実装コード、および活用シナリオ

概要と核心概念

代理パターンは、GoFに归类される構造型デザインパターンの一種です。このパターンは、特定のオブジェクトに対するアクセスを制御・監視するためのラッパー(プロキシ)を中間に配置することを目的としています。クライアントは実際に処理を行う本体のオブジェクトを意識することなく、常にプロキシ経由でインタラクションを行います。

システムアーキテクチャにおいてよく見られるのは、ネットワーク越しのリソースや高負荷のプロセスに対して、前方にキャッシュノードや認証ゲートウェイを設置する設計です。プロキシ層は既存のビジネスロジックを変更することなく、アクセス制御、リソース遅延確保、実行メトリクス計測などの横断的関心事を独自に肩代わりします。

パターン構成要素

  • 抽象サブジェクト(Interface/Base Type):プロキシと本来の実装体が共通して満たすべき契約を定義します。クライアントはこの抽象型のみを参照します。
  • リアルサブジェクト(Concrete Implementation):実際のドメインロジックや高コストな処理を担う本体クラスです。
  • プロキシ(Proxy Wrapper):抽象型を実装し、内部にリアルサブジェクトへの参照を保持します。アクセスの可否判断や結果の再利用など、追加の制御フローを実装します。

特性とトレードオフ

主な利点:

  • 開閉原則(OCP)に基づき、既存クラスの修正なしに振る舞いを拡張可能
  • クライアント側から実体生成やネットワーク接続の詳細を完全に隠蔽
  • 依存関係の注入やライフサイクル管理を柔軟に行える

潜在的な課題:

  • 階層が増加するため、追跡・デバッグ範囲が広がるリスク
  • 不適切なプロキシ実装は予期せぬオーバーヘッドや循環参照を引き起こす

代表的な適用シナリオ

  • バリューナルプロキシ(Virtual Proxy):画像や大規模データベース接続など、初期化に時間がかかるオブジェクトに対し、最小限の占位子を提供し、必要になった瞬間に実体化させます。
  • プロテクトプロキシ(Protective Proxy):管理者権限や機密データへのアクセスを検証し、認可されていないユーザーからの直接的な操作をブロックします。
  • スマートリファレンス(Smart Proxy):メソッド呼び出し前後にトランザクション管理、ロック機構、パフォーマンス計測などを自動的に差し込みます。
  • リモートプロキシ(Remote Proxy):RPCやRESTful APIなどのネットワーク通信を、ローカルのオブジェクト呼び出しと同じ構文で扱えるように仲介します。

C# による実践的実装例

以下は、キャッシュ機能と実行ログ記録を組み合わせたプロキシの構成例です。本来の重たい計算タスクを分離し、プロキシ側で結果の再利用やメタデータ収集を行っています。

// 抽象インターフェース:データ提供サービスの契約定義
public interface IDataProvider
{
    string Retrieve(string queryParam);
}

// 実装体:実際のハイスループット処理を担当
public class HeavyCalculationEngine : IDataProvider
{
    private readonly string _dataSourceUri;

    public HeavyCalculationEngine(string uri) => _dataSourceUri = uri;

    public string Retrieve(string queryParam)
    {
        // シミュレーション:CPU集約型の演算処理
        System.Threading.Thread.Sleep(800);
        return $"[RESULT] Id={queryParam} DB={_dataSourceUri} ExpensiveOp=Done";
    }
}

// プロキシ:キャッシュ管理とフック処理を統合
public class CalculationProxy : IDataProvider
{
    private readonly ConcurrentDictionary<string, string> _resultCache = new();
    private readonly HeavyCalculationEngine _realEngine;
    private readonly bool _verboseMode;

    public CalculationProxy(bool verbose, string engineConfig)
    {
        _verboseMode = verbose;
        _realEngine = new HeavyCalculationEngine(engineConfig);
    }

    public string Retrieve(string queryParam)
    {
        if (_verboseMode) LogEntry($"Incoming Query: {queryParam}");

        // キャッシュ存在確認による早期リターン
        if (_resultCache.TryGetValue(queryParam, out var hit))
        {
            if (_verboseMode) LogEntry("Cache Hit");
            return hit;
        }

        // コアエンジンへの委譲実行
        var computedValue = _realEngine.Retrieve(queryParam);

        // 結果ストア
        _resultCache.TryAdd(queryParam, computedValue);
        return computedValue;
    }

    private void LogEntry(string message) =>
        Console.WriteLine($"[PROXY] {DateTime.Now:HH:mm:ss} | {message}");
}

// クライアント統合コード
public class AppHost
{
    public static void Execute()
    {
        var proxy = new CalculationProxy(verbose: true, "PostgreSQL_Prod_Cluster");

        Console.WriteLine(proxy.Retrieve("QRY_A1"));
        Console.WriteLine(proxy.Retrieve("QRY_A1")); // キャッシュ参照のため即座に返答
    }
}
[PROXY] 14:32:01 | Incoming Query: QRY_A1
[PROXY] 14:32:02 | Cache Miss
[RESULT] Id=QRY_A1 DB=PostgreSQL_Prod_Cluster ExpensiveOp=Done
[PROXY] 14:32:02 | Incoming Query: QRY_A1
[PROXY] 14:32:02 | Cache Hit
[RESULT] Id=QRY_A1 DB=PostgreSQL_Prod_Cluster ExpensiveOp=Done

現代のフレームワーク内での役割

.NET エコシステムや現代的な Web アーキテクチャにおいて、代理パターンは多数の標準機能の土台となっています。DI コンテナによるサービス登録時のラッパー処理、Entity Framework のプロキシ作成機構、ASP.NET Core Middleware チェーンの各ステーション、さらには gRPC や SignalR のクライアント SDK は、全て本質的にプロキシの考え方を応用しています。これらの抽象化レイヤーを活用することで、ドメインロジックに依存しない再利用可能なインフラストラクチャ部品を構築し、システムの拡張性と堅牢性を飛躍的に高めることが可能です。

タグ: デザインパターン 代理パターン C# .NET オブジェクト指向設計

10月4日 01:42 投稿