継承(Java)
継承
はじめに
ここまで結構長い道のりになってしまいました。他の開発言語でやるような処理については一通り説明してきたつもりですが、「Javaではこういう風にやるんだ」というイメージがつかめてもらえていたらうれしいです。手続き型言語(COBOL、C、FORTRANなどなど)の開発手法として、構造化手法というのがありますが、ここではプログラムの核心を「順次処理(sequence)」、「選択(selection)」、「繰り返し(iteration)」としています。ここまでの説明でふれてきました。ここから2回にわたってオブジェクト指向的な2つの要素(継承とインターフェイス)についてお話しようと思います。オブジェクト指向の中の概念の1つに「ポリモーフィズム(多態性)」という難しそうな考え方があります。これは、プログラムでいえば「同じメソッド名で複数の振る舞いが定義できる」ということを意味しています。ポリモーフィズムを必要とするかどうかは、設計次第ですので、ここでは、継承とインターフェイスのsyntaxを中心にお話をしようと思います。
継承
継承とは、「クラスの機能拡張などのために、その特性を引き継いだ子供のクラスをつくること」を意味しています。Javaでは「単一継承」といって「子供となるクラスは複数の親クラスをもてない」という決まりがあります。(私はC++は書けないのですが、C++では多重継承ということができるようです)。継承はクラスの宣言部で、「この人を拡張しますよ」という形式で宣言します。形式は以下です。
修飾子 class クラス名 extends 親クラス名{このように親クラスを拡張した場合、子供クラスは親クラスの全てのメソッドやメンバー変数を使用することができます(ただし、親クラスでprivate宣言されているものは、直接的にはアクセスすることはできません)。したがって、子供のクラスは、親の全ての資産を継承し、それになんらかの拡張を施せることになります。
…
}
また、親のメソッドをそのまま使うのではなく、それを上書きすることも可能で、これは一般にオーバーロードといいます。これは、子供のクラスで親と同じシグナチャをもつメソッドを定義するだけで行うことができます(同じシグナチャとは、同じメソッド名+引数を意味します)。
それでは、ためしにActorクラスを継承して、SuperActorクラスを作ってみましょう。
package test.basic.bean;
public class SuperActor extends Actor {
public SuperActor() {
super();
}
public SuperActor(String str) {
super(str);
}
public String action() {
return this.superAction();
}
public String superAction(){
super.setAction("10回転ジャンプをする");
return super.action();
}
public String parentAction(){
return super.action();
}
}
練習用ですから、わざと分かりにくく作ってみました。action()メソッドはActorクラスのそれを上書き(オーバーライド)し、SuperActorクラスのsuperAction()を呼び出すようになっています。この結果、SuperActor.action()はActor.action()とは別の挙動をとります。また、コンストラクタはActorのものをそのまま使っています。このSuperActorクラスにactionをあらわすメンバー変数がないことに注意してください。コンストラクタでは、Actorで定義されているメンバー変数(action)に文字列(デフォルトのコンストラクタでは「宙返りをする」)をセットすることになります。ここで、super.メソッド名はActorのメソッド、this.メソッド名はSuperActorのメソッドをあらわしています。
それではこれを使うメインプログラムを作ってみましょう。Sample020を作ってみましょう。
package test.basic;
import test.basic.bean.SuperActor;
public class Sample020 {
public static void main(String[] args) {
SuperActor actor;
String action;
// デフォルトのコンストラクタの呼び出し
actor = new SuperActor();
System.out.println(actor.action());
actor.setAction("3回転ジャンプをする");
System.out.println(actor.parentAction());
}
}
これを実行すると以下のような結果がでます。
10回転ジャンプをする
3回転ジャンプをする
最初の出力は以下のセンテンスで出力されていますが、actionメソッドはSuperActor.action()が呼び出されています。
System.out.println(actor.action());
また、2番目の出力は以下のセンテンスから出力されています。setAction()メソッドはSuperActorでは定義されてませんので、Actorのものが使用されます。SuperActor.parentAction()はActor.action()を呼び出していますので、以下の結果が得られます。
actor.setAction("3回転ジャンプをする");
System.out.println(actor.parentAction());
今回の例では、Syntaxの説明というところに主眼をおいて分かりづらい例を作りました(実際には、こんな分かりにくいプログラムを作る人はいませんよね。悪い設計の手本です)。
実際の現場では、「祖先になるべきクラスに多くのクラスを持たせ、子供のクラスの実装を軽くする(その結果として、ビジネスロジック部のコーディング量をへらす)」などの戦略がとられます。しかし、この戦略は、そのクラスの設計変更が全ての子クラスに及ぶなど、かならずしも良い面ばかりをもつわけではありません。
また、他のクラスに仕事を任せることを「委譲」といいますが、このような戦略をとるよりは、多くのクラスに委譲をさせる設計の方がよいなどとも言われます(継承より委譲)。
GoFにより提唱されたデザインパターンは、1つの設計のベストプラクティスですが、この根本にあるのは継承より、インターフェイスの活用(そして、委譲)であると思えます。ですが、このうちで「テンプレートメソッド(template method)パターン」は継承をうまく活用している例といえます。
2007年02月26日(月) 15:36:55 Modified by wanderingse