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

昨日「拡張メソッドを使ったtraitあるいはミックスイン(mixin)的なもの(その2) - Jamzzの日々」の最初の投稿した後にお風呂に入ってあれこれ考えていたらメモリリークに気がついて、ひょっとして参考にする人がいるかもしれないと思って取り急ぎソースコードを訂正しました。
しかしやはりDispose()で破棄しなければならないのはあんまりなので、インスタンス変数はインスタンスに持たせるべきだと考えました。
それで改めてインターフェイスではプロパティとして宣言して実装クラスに用意してもらうという方法で「第4回 Scala言語を探検する(2)(2ページ目) | 日経 xTECH(クロステック)」にある「Traitでアスペクト指向を簡易に実現」の例を実現してみました。

    interface ChangeableAction
    {
        Action doAction { get; set; }
    }

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

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

    class SuperSome
    {
        void SayHello()
        {
            Trace.WriteLine("Hello!");
        }
    }
    class RealAction : SuperSome, ChangeableAction
    {
        #region ChangeableAction メンバ
        private Action action;
        public Action doAction
        {
            get { return action; }
            set { action = value; }
        }
        #endregion

        public RealAction()
        {
            action = new Action(thisAction);
        }

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

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

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

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

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

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

      • -

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

      • -

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

実装クラスにインスタンス変数を提供してもらうお約束が必要になりますがインターフェイスを実現するという自然で単純なお約束なので個人的にはこんなところだろうと思っています。
少なくともtraitに惑わされた最初のソースよりは良くなったと思います。


さてここまで試行錯誤してみた感想としては、やはりスコープを限定して後付けで機能を合成する手段としてScalaのtraitは洗練されていてスマートな機能だと思います。
C#でもサポートされれば利用してみたいと思います。
ただ現状でもほんのちょっと野暮な表現と手間を掛けるだけでtraitと同様の設計のアイデアが実現できるので今回の検証で新しい設計のアイデアを得ることができて良かったと思っています。