委譲の自動化
今書いているコードは、物語記述言語のライブラリだ。ライブラリというか言語機能だ。
CharaSwitch( Case(Chara1, "Chara1のセリフ", Stop, //Click待ち "主人公のセリフ", Wait(100), //一秒動作を止める ), Case(Chara2, "Chara2のセリフ", Stop, ... ) );
なんかこんなのが書きたい。言語機能のように良く働くメソッドが作りたい。
C#では、インスタンスメソッドはobj.Method();という形になるし、スタティックメソッドだとClassName.Method()という形になる。前に余計なモノがついてしまうので、言語機能っぽく見えないし使いづらい。
メソッド名を直接書いて呼び出せるのは、自分のクラス内のメソッドと、内部クラスの場合その外側のクラスのメソッドだけだ。
全部をライブラリクラスの内部クラスとして作るというのも、partialクラスによって現実味を帯びてきたが、本当にコンパイル速度は大丈夫なのかというところがちょっと気になるのと、実行できる言語機能がエピソードによって異なるというのがこれだと上手く表現できないというのがある。
キャラが二人しかいないエピソードだったら、三人目にアクセスするメソッドは全てつかえない。使えないのでそういうメソッドは定義されていないほうがいい。三人のエピソードを書く場合以外では、三人目にアクセスするメソッドは呼び出せないようにしたい。
そのためにはメソッドを定義しなければいい。というわけで、一人のエピソードを書くクラス、二人のエピソードを書くクラス、といった感じで分けて、それぞれに定義されたメソッドが異なるという状況を作りたい。クラス内でメソッドを書けば頭に余計なものはつけなくて済む。
しかし二人のエピソードにもいろいろ種類があり、三人のエピソードにもまた種類がある。メソッドの本体はどこか別のところに書いておき、それを委譲して呼び出すだけにしても、メソッド数は多いし、エピソードの種類も多い。委譲するのも手間だ。
で、継承を用いて作ることになる。しかし作ってて気付いたのだが、エピソードの使用するメソッドはいろいろな分割が出来、それらを多重継承してエピソードのライブラリクラスは作られる。委譲コードをきっちり再利用しようと思ったら、多重継承が必要になる。
C#では多重継承が出来ないので、委譲でどうにかすませることになる。しかし委譲コードが膨大になり、手作業では実際問題実装が限りなく不可能に近いというケースも結構出てくる(http://d.hatena.ne.jp/pmoky/20061209/p1)。今回のケースは委譲コードがやばいくらいいっぱい出てくるというのが問題なので、委譲でどうこうするというのは無理だ。
多重継承はなくていいけど、委譲を自動化する仕組みがあったらいいなあと思う。プロパティの自動実装はC# 3.0でやるとかやらないとかいう話だが、委譲の自動実装もやってほしいものだ。委譲の自動化はDelphiにはあったらしい(良く知らない)から、近い将来のC#に取り入れられるのではないかと思う。
Dephiの委譲の自動化を俺が分かっている範囲で書いてみる(全然違ってるかも)。
public interface IHoge{ int Property{get; set;}; string Method(object x); } //IHogeを実装したクラス public class Hoge : IHoge{ //プロパティの自動実装。 public int Property{ get; set; } /*これと等価 int property; public int Property{ get{ return property; } set{ property = value; } } */ string Method(object obj){ return obj.ToString(); } } //Hogeを自動で委譲するクラス public class JidouIjou { Hoge hoge = new Hoge(); //プロパティで移譲先を指定する Hoge Hoge{ get{ return hoge; } } implements IHoge //implements interface名で自動的に委譲。 } /*これと等価 public class JidouIjou : IHoge //自動委譲した場合interfaceを自動で継承 { Hoge hoge = new Hoge(); Hoge Hoge{ get{ return hoge; } } public int Property{ get{ return Hoge.Property; } set{ Hoge.Property = value; } public string Method(object obj){ return Hoge.Method(obj); } } */ //自分で動作をカスタマイズしたい場合 public class JidouIjou { Hoge Hoge{ get{ return hoge: } } implements IHoge //IHogeのメソッドをMyMethodに置き換え string IHoge.Method(object obj) = MyMethod; string MyMethod(object obj){ return obj.GetType().Name; } } //二つのinterfaceを自動委譲して名前がかちあってしまう場合 public class JidouIjou { Hoge Hoge{ get{ return hoge; } } implements IHoge Hoge2 Hoge2{ get{ return hoge2; } } implements IHoge2 //IHogeとIHoge2に同名同引数のMethodがある。 //普通のC#でいうinterfaceの明示的実装で解決できるだろう。暗黙的に、こうする。 string IHoge.Method(object obj){ return Hoge.Method(obj); } string IHoge2.Method(object obj){ return Hoge2.Method(obj); } //IHogeにキャストされてたらIHoge.Methodが、IHoge2にキャストされてたらIHoge2.Methodが呼び出される。JidouIjouクラスにはpublicにはMethodというメソッドは存在しない。インターフェース越しの場合のみ呼び出される。 //interfaceの明示的実装では、publicにMethodが欲しかったら、実際にpublicメソッドを作り、どっちかを選ぶかを自分で実装して決めることになる。 public string Method(object obj) { //JidouIjouクラス的にはHoge2を採用。 return Hoge2.Method(obj); } */ //自動実装だとこの辺が隠されてしまうので、こんな感じに書かされるだろうか。この辺Delphiでどうなっているのかも良くわかっていない。 public string JidouIjou.Method = Hoge2.Method;
プロパティで委譲先を決めるので、委譲オブジェクトの生成を遅延したり、実行時に委譲先を変えたりできる。多重継承よりも広い仕組みといえるかもしれない。
あと、多分implementsはC#的には無しのキーワードだから、C#だと
public class JidouIjou { public Hoge Hoge{ get; } : IHoge }
こうかもしれない。