純粋仮想基底クラスと図形の課題の基本形
課題で取り組んでいるプログラムは、純粋仮想基底クラスFigureと、そこから派生させたクラスと仮想関数で絵を書いている。このような派生の関係を以下のような図で表現する。
class Figure { public: virtual void draw( int x , int y ) = 0 ; } ; class FigureBox : public Figure { private: int width , height ; public: FigureBox( int w , int h ) : width( w ) , height( h ) {} virtual void draw( int x , int y ) { // 四角を描く処理 } } ; class FigureCircle : public Figure { private: int radius ; public: FigureCircle( int r ) : radius( r ) {} virtual void draw( int x , int w ) { // 円を描く処理 } } ;
色付き図形を派生する方法
課題では、上記プログラムを活用して、色付き図形のクラスを作ることを目的とするが、その実装方法には色々な方法がある。
単純なやり方は、FigureBox と同じように、Figure から FigureColorBox を派生させる方法だろう。
class FigureColorBox : public Figure { private: int width , height , color ; public: FigureColorBox( int w , int h , int c ) : width( w ) , height( h ) , color( c ) {} virtual void draw( int x , int y ) { // 色を変える処理 // 四角を描く処理 ... FIgureBox と同じ処理 } } ;
この方法は単純だけど、四角を描く処理を書くため無駄であり、FIgureBox と処理を共通化できればプログラムを書く手間を減らせるはず。
処理を共通化するなら派生すればいい
class FigureColorBox : public FigureBox { private: int color ; // 幅と高さの記述が無い public: FigureColorBox( int w , int h , int c ) : FigureBox( w , h ) , color( c ) {} virtual void draw( int x , int y ) { // 色を変える処理 FigureBox::draw( x , y ) ; // 親クラスの処理を継承 } } ;
同じような色の処理を追加したクラスが沢山ある場合
上記の、FigureBox から FigureColorBox を派生させた場合は、四角を描く処理が継承により共通化ができた。
しかし、同じように FigureCircle から FigureColorCircle を派生させる…といったクラスを沢山作る場合は、色を変える処理を何度も使うことになるが、処理の共有することができない。こういった場合は、以下のような方法がある。
class Color { private: int color ; public: Color( int c ) : color( c ) {} void set_color() { // 色を変える処理 } } ; class FigureColorBox : public FigureBox , public Color { public: // 多重継承のキモ FigureColorBox( int w , int h , int c ) : FigureBox( w , h ) , Color( c ) {} virtual void draw( int x , int y ) { Color::set_color() ; // Colorクラスを使って色を設定 FigureBox::draw( x , y ) ; // FigureBoxクラスで形を描く } } ;
多重継承
上記の FigureColorBox のように、親クラスとして、FigureBox と Color のように複数のクラスをもつ継承は、多重継承と呼ばれる。
ただし、多重継承は後に示すような曖昧さや、実装の際の手間を考えると、多重継承は必ずしも便利な機能ではない。このため、オブジェクト指向のプログラミング言語によっては、多重継承機能が使えない。
Java では、多重継承が使えない代わりに、interface 機能が使えたりする。
ダイヤモンド型の継承と曖昧さ
C++ のような言語での多重継承が問題となる事例として、ダイヤモンド型の継承が挙げられる。
例えば、動物クラスから鳥クラス・哺乳類クラスを派生して、鳥クラスからニワトリを派生して、哺乳類クラスから人間を派生するというのは、進化の過程を踏まえると、自然な派生と考えられる。
しかし、鳥がクラスメンバーとして羽と足を持ち、哺乳類が手と足を持つとしたとき、鳥のくちばしを持ち卵を生むカモノハシを派生する場合には、どうすれば良いのだろうか?しかし、これを鳥と哺乳類を親クラスとした多重継承で実装をすると、手と羽と足が4本のカモノハシができてしまう。(お前はドラゴンかッ!)
また、大元の動物クラスがインスタンスを持つ場合、このような多重継承をすると、同じ名前のインスタンスを2つ持つことになる。この場合、C++では仮想継承というメカニズムを使うことができる。
class Animal { private: char name[10] ; } ; class Bird : public virtual Animal { } ; class Mammalian : public virtual Animal { } ; class Psyduck : public Bird , public Mammalian { // このクラスは、name は1つだけ。 } ;
また、動物が動くためのmove()というメソッドを持つとした場合、鳥は羽で飛び、哺乳類は足で移動する処理となるだろう。しかし、多重継承のカモノハシは、鳥の羽で動くメソッドmove()と、哺乳類の足で動くメソッドmove()を持つことになり、カモノハシに動け…と命令した場合、どちらのメソッドmove() を使うのだろうか?
こういった、使いにくさ・実装時の手間・処理の曖昧さを考慮したうえで、多重継承のメカニズムが使えるオブジェクト指向プログラム言語は少ない。