前回までの範囲でカプセル化による、プログラムがわかりやすくなるといった事例の 紹介。これ以降の継承などを使えるようになれば「車輪の再発明」と言われるような、 他人が作ったものと同じようなものを、また作らないといけない… ということが減らせるはず。
派生の必要となりそうな事例ということで、基本データにオプショナルな要素が 加わったデータ事例にて、問題点を示す。
派生がないと発生する手間
// C言語でオプショナルな要素の加わった場合。 // 基本型: 名前・身長 // 追加データ:親の情報 struct Person { char name[ 20 ] ; double height ; } ; void print_Person( struct Person* p ) ; // ダメダメな追加データの型 struct Child { char name[ 20 ] ; double height ; struct Person* parent ; // 追加データ } ; // この方法では、同じようなプログラムをまた作ることになる。 void print_Child( struct Child* p ) { また同じ処理を書く羽目になる... } // ちょっとマシな追加データ型 struct Child { struct Person super ; // 基底クラス struct Person* parent ; } ; // 同じ処理を書くよりはマシだけど、 // 基底クラスのメソッド呼出しを何度も書く必要がある。 void print_Child( struct Child* p ) { print_Person( &(p->super) ) ; }
導出を使って
この様に、オプショナルなデータの増えたプログラムで手抜きができるために、 オブジェクト指向言語では、派生(導出)といった方法が使われる。
class Person { private: char name[ 20 ] ; double height ; public: Person( char s[] , double h ) { strcpy( name , s ) ; height = h ; } void print() { printf( "%s %f" , name , height ) ; } } ; class Child : public Person { private: Person* parent ; public: Child( char s[] , double h , Parent* p ) : Person( s , h ) { parent = p ; } } ; void main() { Person saitoh( "斉藤" , 172 ) ; saitoh.print() ; Child mitsuki( "みつき" , 130 , &saitoh ) ; mitsuki.print() ; // 基底クラスの print を継承し、流用してくれる。 Person tomoko( "ともこ" , 148 ) ; Child ayuka( "あゆか" , 122 , &tomoko ) ; Person* family[ 4 ] ; family[0] = &saitoh ; family[1] = &mitsuki ; // ChildがPersonに格下げして代入 family[2] = &tomoko ; family[3] = &ayuka ; // Personに格下げ // 家族全員を表示 for( int i = 0 ; i < 4 ; i++ ) // PersonとChildが混在するけど family[ i ]->print() ; // 正しく表示ができる。 }
問題提起
前述のChildクラスでは、printメソッドは継承を利用していたが、 Child クラス用の処理を書いたとする。すると、格下げの問題が表面化してくる。
class Child : public Person { : void print() { // 子供は、親の名前も表示したかったとする。 Person::print() ; printf( "%s" , parent->name ) ; // "public Person" で派生したから } // できる芸当である。 } ; // 家族全員の表示。 for( int i = 0 ; i < 4 ; i++ ) family[ i ]->print() ; // この結果では、family に代入する時点で、 // Child も Person に格下げされているので、 // mitsuki の親の名前は表示されない。 -----> 仮想関数で解決できる。