第 7 回 オブジェクト指向プログラミング (3)

今回の内容

共通性の操作を持つクラス

例えば、「画面に描くことができる」クラスが複数作りたいとしましょう。 それらクラスはそれぞれ void paint(Graphics g) のような持つことになりますが、 プログラムにおいて、画面への描画という共通の操作だけが必要となる箇所では それらクラスを区別せず同じものとして扱いたいことがあります ()。

JAVAにおいてそのようなプログラミングをする方法は二つあります。 順に見ていきましょう。

  1. 同じクラスを継承したクラスとする

    下図のような継承関係のクラスを作り、child1とchild2に共通する部分を クラスparentに書いておくことにより実現できます。

    parent--+--child1
            |
            +--child2
    

    child1とchild2のインスタンス同士を共通に扱いたいときは、 どちらもparentクラスのインスタンスだとして扱えばよいのです。 child1とchild2で共通のメソッド(例えばdraw)の中身が異なっても、 それぞれのクラスで上書きしてしまえばよいです。 なぜなら、parentクラスのインスタンスとして扱っても、 実際のクラスに応じたメソッドが使われるからです。

    ここで問題となるのは、 「parentクラスは、childクラスに共通性をもたせるためだけの存在なので、 そのインスタンスを作ることはない」というような場合です。 自分でプログラミングするときにインスタンスを作らないように 注意すれば問題ないとも言えますが、 JAVAではインスタンスを作らないクラスを明示することができます。 そのようなクラスを抽象クラスといいます。

    abstract class parent{
      //抽象クラスの例(中略)
               :
      //abstractなメソッドは定義だけ書く(最後にセミコロンを)
      abstract void draw(Graphics g);
    }
    

    同じクラスを継承させるという方法はとても有用ですが、大きな弱点があります。 下図のような場合です。

    parent1--+--child1
             |
             +--child2
    
      //child1はさらにchild3とも共通性を有する
      //下のようにするとよさそうだ。
    parent2--+--child1
             |
             +--child3
      

    しかし、JAVAでは一つのクラスは一つのクラスしか継承することができないので、 上のようなことはできません。それを解消するのが以下に述べる「インターフェース」です。 (複数のクラスを継承することを「多重継承」といいます。 多重継承が可能なプログラミング言語もあります。C++など。 便利ではあるのですが、危ない側面 を持つので、JAVAではできないようになっています。 このあたりは趣味の出るところであり、嫌ならC++に乗り換えろということです。)

  2. 同じインターフェースを実装したクラスとする

    インターフェースというのは、メソッドの定義をいくつかもったものです。 空っぽのクラスという感覚が近いと思います。 インターフェースに定義されたメソッドを実装(実際の処理内容を記述する) した複数のクラスのインスタンスはそれぞれ同じクラスのインスタンスであるかのように扱うことができます。 また、インターフェースはいくつでも実装することができるので、上に挙げた問題をクリアすることができます。 インターフェースに関わる文法事項を以下に挙げます。

      //インターフェースの定義の例
    Interface movable{
      //メソッドの定義のみ
      public void move(int x, int y);
    }
    
      //インターフェースの実装の例
    class child1 extends parent implements drawable{
      public void move(int x, int y){
        //実装する
      }
    }
    
    class child3 implements drawable{
      public void move(int x, int y){
        //実装する
      }
    }
    

    これでchild1とchild3に共通性をもたせることができました。 今、同じインターフェースinterface1を実装したクラスのインスタンスへの参照は以下のように、 interface1型の参照として扱うことができます。

    child1 a;
    child3 b;
    interface1 c;
    
      //違うクラスのインスタンスへの参照を
    a = new child1();
    b = new child3();
    c = new child1();
    
      //同じメソッドの引数とすることができる
    moveNTimes(3,a);
    moveNTimes(2,b);
    moveNTimes(5,c);
    
      //メソッドmoveを持つクラスを引数にとれるよう
      //設計されたメソッド
    void moveNTimes(int n, interface1 a){
      for(int i = 0; i < n; i++){
          //interface1を実装しているので、moveメソッドを持っている
        a.move(2,2);
      }
    }