Home>C# Tips>

RealProxyを使ったアダプタパターン

更新日:2003/08/17

デザインパターンのアダプタパターンです。普通に委譲で実装するとメソッドの横流しをちまちま書くことになって面倒です。RealProxyを使うとその作業がいらなくなって便利です。取りあえずパラメータの順序変更や減少には対応しますが、パラメータの増加には未対応です。


クラス構成

AdapterProxy
任意の型に対してインタフェースの適応を行います。
Keisan
計算クラス。昔の古いライブラリというシナリオです。
ICalc
新しいインタフェース。Keisanクラスの実装にかぶせるアダプタとなります。
MyApp
AdapterProxyの使用例。

adapter.cs

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Reflection;

namespace Mei
{
  /// <summary>
  /// ReadProxyによるアダプタパターン。
  /// </summary>
  public class AdapterProxy : RealProxy {
    object adaptee;
    Hashtable hashName = new Hashtable();
    Hashtable hashOrder = new Hashtable();

    /// <summary>
    /// コンストラクタ。
    /// </summary>
    /// <param name="type">アダプタとなるインタフェース</param>
    /// <param name="adaptee">実装を持つクラス</param>
    public AdapterProxy(Type type, object adaptee) : base(type) {
      this.adaptee = adaptee;
    }

    public object Adaptee {
      get { return adaptee; }
    }

    /// <summary>
    /// メソッドの適応方法を指定する。
    /// </summary>
    /// <param name="from">インタフェース側のメソッド名</param>
    /// <param name="to">実装側のメソッド名</param>
    /// <param name="argOrder">実装側に渡すインタフェース側のパラメータ位置</param>
    public void Adapt(string from, string to, int[] argOrder) {
      hashName[from] = to;
      hashOrder[from] = argOrder;
    }

    /// <summary>
    /// メソッドの適応方法を指定する。
    /// </summary>
    /// <param name="from">インタフェース側のメソッド名</param>
    /// <param name="to">実装側のメソッド名</param>
    public void Adapt(string from, string to) {
      this.Adapt(from, to, null);
    }

    public override IMessage Invoke(IMessage msg) {
      IMethodMessage mm = msg as IMethodMessage;
      string toMethod = hashName[mm.MethodName] as string;

      if (toMethod == null)
        toMethod = mm.MethodName;  // メソッドは同名

      // 変換先のシグネチャ
      Type[] fromSig = mm.MethodSignature as Type[];
      Type[] toSig;
      int[] order = hashOrder[mm.MethodName] as int[];
      object[] toArgs;

      if (order == null) {
        // 引数の順序は同じ
        toSig = fromSig;
        toArgs = mm.Args;
      }
      else {
        // 引数の順序が異なる
        int pos;
        toSig = new Type[order.Length];
        toArgs = new object[order.Length];
        for (int i = 0; i < order.Length; ++i) {
          pos = order[i];
          toSig[i] = fromSig[pos];
          toArgs[i] = mm.Args[pos];
        }
      }

      MethodInfo mi = adaptee.GetType().GetMethod(toMethod, toSig);

      return new ReturnMessage(mi.Invoke(adaptee, toArgs), null, 0, 
        mm.LogicalCallContext, (IMethodCallMessage)msg);
    }
  }

  /// <summary>
  /// 計算を行う古いクラス。
  /// </summary>
  class Keisan {
    // x + y
    public double Tasu(double x, double y) {
      return x + y;
    }
    // y - x (引数を逆にしている)
    public double Hiku(double x, double y) {
      return y - x;
    }
  }

  /// <summary>
  /// Keisanクラスに対するアダプタ・インタフェース
  /// </summary>
  interface ICalc {
    // x + y
    double Add(double x, double y);
    // x - y
    double Subtract(double x, double y);
  }

  /// <summary>
  /// メインクラス
  /// </summary>
  class MyApp
  {
    [STAThread]
    static void Main(string[] args)
    {
      // ICalcインタフェースを通してKeisanクラスへアクセスさせる
      AdapterProxy adapter = new AdapterProxy(typeof(ICalc), new Keisan());

      // AddをTasuへ委譲、引数の順序は変わらず。
      adapter.Adapt("Add", "Tasu");
      // SubtractをHikuへ委譲、引数の順序をx,y -> y,xにして渡す。
      adapter.Adapt("Subtract", "Hiku", new int[] {1 /* y */, 0 /* x */});
      // 透過プロキシを取得。
      ICalc calc = adapter.GetTransparentProxy() as ICalc;

      // 計算
      double x = 1.23;
      double y = 4.56;
      Console.WriteLine("{0} + {1} = {2}", x, y, calc.Add(x, y));
      Console.WriteLine("{0} - {1} = {2}", x, y, calc.Subtract(x, y));
    }
  }
}

Home>C# Tips>