拡張メソッドを使ったtraitあるいはミックスイン(mixin)的なもの(その2)

拡張メソッドを使ったtraitあるいはミックスイン(mixin)的なもの(その1) - Jamzzの日々」に引き続いて、「第4回 Scala言語を探検する(2)(2ページ目) | 日経 xTECH(クロステック)」にある「Traitでアスペクト指向を簡易に実現」の例をC#で実現してみました。
あらかじめ断っておきたいのですがこの実装はScalaのtraitの機能をC#で実現するというわけではなくてあくまでも上記のサンプルで実現している機能をC#で実現することを考えたものです。
くどいようですがそれぞれの言語の良し悪しを言いたいのではなく、それ以前に設計上の意図とその実現方法の違いを検証するものです。


私は不勉強で完全な定義を理解していないのですが、私の理解ではScalaのtraitというものは

  • overrideされるメソッドの親子関係を実行時に構成する
  • ミックスイン的な機能の共有

の2つのことを実現している様に見えます。
ここで「overrideされるメソッドの親子関係を実行時に構成する」に関してはC#ではdelegateを使って実装が任意に差し替えられるメソッドで実現することを考えました。
機能を差し替えられるdoAction()というメソッドを持つことはChangeableActionインターフェイスとその拡張であるChangeableActionExtentionクラスで実現しています。
また「ミックスイン的な機能の共有」については拡張メソッドの機能で実現しました。
その時点でのdoAction()の事前事後の処理を挿入する機能はBeforeAfterExtentionクラスのWithBeforeAfter()メソッドで実現しています。
その時点でのdoAction()を2回実行する処理を挿入する機能はTwiceExtentionクラスのWithTwice()メソッドで実現しました。

    interface ChangeableAction : IDisposable
    { }
    static class ChangeableActionExtention
    {
        private static Dictionary<object, Action> actions = new Dictionary<object, Action>();

        public static Action GetAction(this ChangeableAction obj)
        {
            return actions[obj];
        }

        public static void ChangeAction(this ChangeableAction obj, Action newAction)
        {
            actions[obj] = newAction;
        }

        public static void doAction(this ChangeableAction obj)
        {
            actions[obj]();
        }

        public static void RemoveAction(this ChangeableAction obj)
        {
            actions.Remove(obj);
        }
    }

    class SuperAction
    {
        void SayHello()
        {
            Trace.WriteLine("Hello!");
        }
    }
    class RealAction : SuperAction, ChangeableAction
    {
        public RealAction()
        {
            this.ChangeAction(new Action(realAction));
        }

        private void realAction()
        {
            Trace.WriteLine("Real Action");
        }

        public void Dispose()
        {
            this.RemoveAction();
        }
    }

    static class BeforeAfterExtention
    {
        public static ChangeableAction WithBeforeAfter(this ChangeableAction obj)
        {
            Action currentAction = obj.GetAction();
            obj.ChangeAction(() =>
                {
                    Trace.WriteLine("Before Action");
                    currentAction();
                    Trace.WriteLine("After Action");
                });
            return obj;
        }
    }

    static class TwiceExtention
    {
        public static ChangeableAction WithTwice(this ChangeableAction obj)
        {
            Action currentAction = obj.GetAction();
            obj.ChangeAction(() =>
            {
                for (int i = 1;i <= 2; i++)
                {
                    currentAction();
                    Trace.WriteLine(" ==> No." + i.ToString());
                };
            });
            return obj;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            RealAction ra;
            using (ra = new RealAction())
            {
                ra.WithTwice().WithBeforeAfter();
                ra.doAction();
                ra.SayHello();
                Trace.WriteLine("----");
            }

            using (ra = new RealAction())
            {
                ra.WithBeforeAfter().WithTwice();
                ra.doAction();
                ra.SayHello();
                Trace.WriteLine("----");
            }

            using (ra = new RealAction())
            {
                ra.ChangeAction(() => Trace.WriteLine("Updated Real Action"));
                ra.WithBeforeAfter().WithTwice();
                ra.doAction();
                ra.SayHello();
            }
        }
    }

この実行結果は以下の通りです。

Before Action
Real Action
After Action
==> No.1
Before Action
Real Action
After Action
==> No.2
Hello!

      • -

Before Action
Real Action
==> No.1
Real Action
==> No.2
After Action
Hello!

      • -

Before Action
Updated Real Action
After Action
==> No.1
Before Action
Updated Real Action
After Action
==> No.2
Hello!

言語がサポートしていない機能を記述しなければならないのでScalaの例より煩雑で手間がかかることは当然として、やはりインスタンス変数が使えない*1こととdoAction()の振る舞いを変えるSetAction()メソッドをpublicにするしかないことが悩ましいところです。
しかし利用する側から見れば自然な表現ができると思っていて個人的にはこんなところ*2だろうと思います。


さて、そもそもC#の拡張メソッドについて調査していたのですがtraitを見つけてちょっとマニアックな検証になってしままいました。
ここまでの検証の結果を踏まえて改めてミックスインとtraitとその設計の意図をC#で実現する方法について整理してみたいと思います。


[2010-04-05]追記:
結局上記のコードはあんまり良くないと思います。
やはりインスタンス変数はインスタンスに持たせるべきだと思います。
そこで改めてインターフェイスではプロパティとして宣言して実装クラスに用意してもらうという方法書き直しました。
参考にされたい方は「拡張メソッドを使ったtraitあるいはミックスイン(mixin)的なもの(その3) - Jamzzの日々」こちらの方を見てください。

*1:最初に投稿したコードではメモリーリークしますのでIDisposableを実装して回避する案で更新しました

*2:オブジェクトのライフサイクルがアプリケーションのライフサイクルと等しい場合にはDispose()しなくても良いですが、こうなってくるとちょっと厳しい感じがします