ポリモーフィズム的な何かについて考える・・・
「同じメッセージに対してオブジェクトごとに異なる処理を実行させたい」と思ったら、真っ先に思い浮かぶのがポリモーフィズムです。
ポリモーフィズムの定義を見ますと、
プログラミング言語の各要素(定数、変数、式、オブジェクト、関数、メソッドなど)について、それらが複数の型に属することを許容する型システムの性質
とか、
メッセージの送信側とメッセージの受信側が動的に決まる性質
といったやや難解な説明の他、そのままズバリ、
同じメッセージに対してオブジェクトごとに異なる処理を実行させること
などと説明されています。
多くのオブジェクト指向プログラミングでは、ポリモーフィズムは以下のようなクラスやインターフェースの継承によって実現されています。
サンプルコード.
//Case 1 クラス継承によるポリモーフィズム
class Program
{
static void Main(string[] args)
{
System.Diagnostics.Debug.WriteLine("Case 1");
ExecCase1(new Case1A());
ExecCase1(new Case1B());
}
static void ExecCase1(Case1 case1)
{
System.Diagnostics.Debug.WriteLine(case1.Method()); //←ここ
}
}
public abstract class Case1
{
public abstract string Method();
}
public class Case1A : Case1
{
public override string Method()
{
return "exec Case1A.Method";
}
}
public class Case1B : Case1
{
public override string Method()
{
return "exec Case1B.Method";
}
}
これを実行すると以下の結果を得ます。
実行結果.
Case 1
exec Case1A.Method
exec Case1B.Method
この手法では、基底クラスやインターフェースでメソッドを宣言しておき、これを継承した複数の派生クラスでそれぞれ異なる実装を定義します。
基底クラス型の変数でインスタンスを取り扱い、メソッドが呼び出されるとそのインスタンスの派生クラスの実装が実行されるという仕組みです。
これに対し、昔のVB(または、COM、ActiveX)やC#のdynamic型ですと、以下のようなレイトバインディングによる名前解決でも実現可能です。
サンプルコード.
//Case 2 ダイナミック型のレイトバインディングによるポリモーフィズム
class Program
{
static void Main(string[] args)
{
System.Diagnostics.Debug.WriteLine("Case 2");
ExecCase2(new Case2A());
ExecCase2(new Case2B());
}
static void ExecCase2(dynamic case2)
{
string res = case2.Method(); //←ここ
System.Diagnostics.Debug.WriteLine(res);
}
}
public class Case2A
{
public string Method()
{
return "exec Case2A.Method";
}
}
public class Case2B
{
public string Method()
{
return "exec Case2B.Method";
}
}
これを実行すると以下の結果を得ます。
実行結果.
Case 2
exec Case2A.Method
exec Case2B.Method
この手法では、基底クラスによる型の制約はなく、どのような型のインスタンスであってもメソッドの呼び出しが可能です。
その際、呼び出したメソッドの名前でインスタンスの型の定義情報を照会し、該当した実装が実行されます。
該当しない場合は、実行時エラーとなります。
型の制約を受けない代わりに、コンパイル時にエラーを検出できないというトレードオフがあります。
上記の2つはもはや古典的と呼べるほど一般的な手法ですが、いずれもクラスベースのポリモーフィズムです。
もし、同じクラスの異なるインスタンスに対して同じメッセージで異なる処理をさせたいなら、インスタンスベースの手法を用いる必要があります。
インスタンスベースのポリモーフィズムは、以下のようにデリゲートを利用して実現可能です。
サンプルコード.
//Case 3 デリゲートを利用したポリモーフィズム
class Program
{
static void Main(string[] args)
{
System.Diagnostics.Debug.WriteLine("Case 3");
ExecCase3(new Case3(delegate{ return "exec Case3A.Method";}));
ExecCase3(new Case3(delegate{ return "exec Case3B.Method";}));
}
static void ExecCase3(Case3 case3)
{
System.Diagnostics.Debug.WriteLine(case3.Method()); //←ここ
}
}
public class Case3
{
public delegate string MethodDelegate();
private MethodDelegate _m;
public Case3(MethodDelegate method)
{
_m = method;
}
public string Method()
{
return (_m != null) ? _m() : "";
}
}
これを実行すると以下の結果を得ます。
実行結果.
Case 3
exec Case3A.Method
exec Case3B.Method
この手法ではデリゲートを使用していますが、もちろんクラスを使って実現することも可能です。
デザインパターンとして知られるStrategyパターンやStateパターンは、オブジェクトのライフサイクルの中で処理を設定するタイミングこそ違いますが、意図するところは同じです。
もはやポリモーフィズムからも外れてきている感がありますが、「同じメッセージに対してオブジェクトごとに異なる処理を実行させる」手法にもいろいろあるものだと改めて感じました。