— boreal-kiss.com

インターフェイスにクラスを継承させる

注意:IEventDispatcherなどすでにインターフェイスが用意されているものについてはもっとスマートな実装方法がある(最下部の追記参照)

***************

インターフェイスはクラスではないのでクラスは継承できない。インターフェイスに他クラスの機能を扱えるようにして、あたかもクラスを継承しているようにする方法。

イントロダクション

親をインターフェイスで定義しておいて、その子クラスを使う場合、プログラム上では直接子クラスへの参照を作るのではなく親インターフェイスへの参照で扱う方が「後々」都合がよい場合が多い。例えばIDoSomethingインターフェイスを継承したDoSomethingクラスを作る場合は以下のように記述してやる。

package {
    import flash.display.Sprite;
    
    //main
    public class main extends Sprite{
        //Defined as Interface
        private var _doSomething:IDoSomething;
        
        public function main(){
            //Creates a DoSomething class.
            _doSomething = new DoSomething();
            _doSomething.do();
        }
    }
}

package{
    public interface IDoSomething{
        function do():void;
    }
}

package{
    public class DoSomething implements IDoSomething{  
        public function do():void{
            //Something is done.
        }
    }
}

さてDoSomethingクラスは”クラス”なので、その他のクラスを継承することができる。例としてEventDispatcherクラスを継承させる。

package{
    import flash.events.EventDispatcher;
    
    public class DoSomething extends EventDispatcher implements IDoSomething{  
        public function do():void{
            //Do something
        }
    }
}

本題

これでDoSomethingクラスはイベントの送受信ができるようになった。ここでようやく本題(の問題)に突入。先ほどmainクラスではDoSomethingクラスを使う際、DoSomethingクラスそのものではなく親インターフェイス(IDoSomething)への参照を用いて使うことにしていた。IDoSomethingへの参照変数に参照されたDoSomethingクラスがイベントを送信したらどうなるか。IDoSomething.dispatchEvent()メソッドはインターフェイスには定義されていないのでコンパイルエラーになる。

package {
    import flash.display.Sprite;
    import flash.events.Event;
    
    //main
    public class main extends Sprite{
        //Defined as Interface
        private var _doSomething:IDoSomething;
        
        public function main(){
            _doSomething = new DoSomething();
            
            //Compile error as no such method is defined in the Interface.
            _doSomething.dispatchEvent(new Event("doSomething"));
        }
    }
}

package{
    public interface IDoSomething{
        function do():void;
    }
}

package{
    import flash.events.EventDispatcher;
    
    public class DoSomething extends EventDispatcher implements IDoSomething{  
        public function do():void{
            //Do something
        }
    }
}

解決策

DoSomethingクラスではたしかに実装されているメソッドなのに、IDoSomethingインターフェイスでは定義されていない。それにもかかわらずmainクラスではIDoSomethingインターフェイスとして扱いたい場合どうするか。「実装したいメソッドを扱えるクラスを戻り値とする」メソッドを新たにインターフェイスに定義してやればよい。上記のEventDispathcerクラスのメソッドを扱いたい場合は、インターフェイスを以下のように再定義してやる。

package{
    import flash.events.EventDispatcher;
    
    public interface IDoSomething{
        function do():void;
        
        //New!!
        function get eventDispatcher():EventDispatcher;
    }
}

そして、実際のメソッド内容を子クラスに追加、戻り値を子クラス自身とする。子クラスはEventDispatherクラスを継承しているところがポイント。

package{
    import flash.events.EventDispatcher;
    
    public class DoSomething extends EventDispatcher implements IDoSomething{
        
        //New!!
        public function get eventDispatcher():EventDispatcher{
            return this;
        }
        
        public function do():void{
            //Do something
        }
    }
}

mainクラスは以下の通り。IDoSomething.eventDispatcher()メソッド経由でEventDispathcer.dispatchEvent()メソッドにアクセス。無事コンパイルが通る。

package {
    import flash.display.Sprite;
    import flash.events.Event;
    
    //main
    public class main extends Sprite{
        //Defined as Interface
        private var _doSomething:IDoSomething;
        
        public function main(){
            _doSomething = new DoSomething();
            
            //Event successfully dispatched!!
            _doSomething.eventDispacther.dispatchEvent(new Event("do"));
        }
    }
}

応用

同じようにして、例えば子クラスにDisplayObjectContainerクラスを継承させて、DisplayObjectContainerクラスを戻り値とするメソッドを定義してやれば、インターフェイスからDisplayObjectContainer.addChild()などを使うことができる。

package {
    import flash.display.Sprite;
    
    //main
    public class main extends Sprite{
        public function main(){
            var example:IExample = new Example();
            var testSprite:Sprite = new Sprite();
            
            //The interface acts as Sprite!!
            example.displayObjectContainer.addChild(testSprite);
        }
    }
}

package{
    import flash.display.DisplayObjectContainer;
    
    public interface IExample{
        function doExample():void;
        function get displayObjectContainer():DisplayObjectContainer;
    }
}

package{
    import flash.display.Sprite;
    import flash.display.DisplayObjectContainer;
    
    public class Example extends Sprite implements IExample{
        public function doExample():void{
        }
        
        public function get displayObjectContainer():DisplayObjectContainer{
            return this;
        }
    }
}

***********************

追記 2008/12/14

やべえ。インターフェイスにはIEventDispatcherインターフェイスを継承させて、子クラスにはEventDispatcherクラスを継承させるだけで無茶苦茶スマートな構造になった。mutaさん thanks!!

package {
    import flash.display.Sprite;
    import flash.events.Event;

    public class main extends Sprite{  
        public function main(){         
            var doSomething:IDoSomething = new DoSomething();
            doSomething.addEventListener(Event.COMPLETE,trace);
            doSomething.dispatchEvent(new Event(Event.COMPLETE));
        }
    }
}

package{
    import flash.events.IEventDispatcher;

    public interface IDoSomething extends IEventDispatcher{
    }
}

package{
    import flash.events.EventDispatcher;

    public class DoSomething extends EventDispatcher implements IDoSomething{
    }
}
6 comments
  1. muta says: 2008年12月15日11:10 AM

    いつも参考にさせて頂いています。
    インターフェースはインターフェースを継承できますので、

    package
    {
    public interface IDoSomething extends IEventDispatcher
    {
    function do():void;
    }
    }

    でも大丈夫ですよ!
    あと、インターフェースなら多重継承も確か出来たと思います。違ったらごめんなさい。
    ご参考まで。

  2. borealkiss says: 2008年12月15日3:34 PM

    ああそっか。これスマートですね。

    しかしこの場合どうなるんだろう。上の場合だと、IDoSomethingを継承するクラスはIEventDispatcherで定義されているメソッドも実装する必要がでてきませんか?ちょっとした労力のような。

  3. borealkiss says: 2008年12月15日4:40 PM

    ああ、子クラスにEventDispatcherを継承させればそれでメソッド実装完了になるのか

  4. {eL_e3 電機は大切に says: 2008年12月29日3:52 AM

    キャストやasを使わない理由はなんですか?

    メソッドを追加して、インタフェースを書き換えるということは、そのインタフェースを実装するクラスすべてにメソッドを追加する必要がでてくるので、かなりバットノウハウだと思います。

  5. borealkiss says: 2008年12月29日6:13 AM

    途中で型変換を行う(つまり場合分けが生じる)のであればわざわざインターフェイスで定義せずとも最初から特定のクラスで定義すればよい話で、わざわざインターフェイスで定義する意義(メソッドの実装が保証されているクラス間の互換性を保ちたい)がありません。

    インターフェイスの書き換えを行っているつもりもありません。ここで綴っているのはあくまでも実装初期段階の話です。

  6. 通りすがり says: 2009年11月19日9:01 PM

    あんまりいい方法でもないような。
    それだと、IDoSomethingを実装するクラスは、すべてIEventDispatcherのメソッドも実装するコストが必要だよね。
    IDoSomethingの役割としてはdo()の実装を保障したいわけで、IDoSomethingインターフェイスの役割的には余計なIEventDispatcherまで実装しなきゃならないのはどうかな。
    IDoSomethingとIEventdispatcherが意味的につながりがあるなら、継承するのも納得いくけど。
    インタフェースとしてインスタンスを保持することにこだわりすぎなんじゃ?

    それぞれ別の役割のインタフェースなんだから、
    それぞれを個別に継承した抽象クラスを作ってそれを使うとか。

Submit comment