13.保育園児の豚汁作り
~継承とポリモーフィズム~

 オブジェクト指向の山場に来ました。 そのわりにタイトルはのんびりしていますが... ここではイメージ作りを先行させ、細かいJavaの文法はやりません。 話が長くなりますが、(関係ないことも入っているかも)さらさら読んでいってください。

園長先生の「豚汁準備はじめ」

 娘の保育園は、ときどき豚汁会やカレー会をやります。 自分たちで作って、自分たちで食べるのです。 もちろんほとんどの作業は先生方がやるのですが、園児たちもちゃんと作業が分担されています。

 まず桃さんは「野菜を洗う」、黄さんは「野菜の皮をむく」、青さんは「野菜を切る」のです。 桃さんは3歳児の桃組の園児の呼び名で、黄さんは4歳児の黄組、青さんは5歳児の青組です。

 それぞれの組では、豚汁作りの手順をあらかじめ先生がみっちり教え込んであります。青さんだとこんな具合かな。 「包丁を持たない方の手は、グーにしてにんじんを押えて下さい。パーでやると指を切っちゃうかもしれないですからねぇ。 それから包丁は振り回しちゃだめですよ...」保育園の先生も大変だ。

 さあお楽しみの豚汁会、お庭になべもセットされています。泥つきの野菜や、皮むき、包丁、まな板も準備OK.。 園長先生が並んだ園児にごあいさつ。 「さあ今日はお楽しみの豚汁会です。 怪我をしないようにがんばっておいしい豚汁を作ってください。さあ豚汁準備始め。」  園児たちは自分のやるべきことをちゃんと心得ていて、それぞれの持ち場に分かれ、やるべきことをどんどんやります。 (中にはボーッと見ている子、泣き出す子もいておもしろい)



 桃さんから青さんまでの3年間は、人生の中で一番成長する時期ではないでしょうか。 そんなたくさんの園児たちをひとまとめで見ろといっても先生方も困ってしまいます。 そこで組に分けているんですね。 だから園長先生は園児全員に「豚汁準備始め」と命令しただけで、 それぞれの園児たちは、組別に教えられたとおりに作業をしに飛んでいくわけです。

 ここで注目してほしいのは、園長先生が「あなたは桃さんね、じゃあ野菜を洗ってちょうだい。 あなたは青さんね、野菜を切ってちょうだい。...」とめんどうなことをいっているわけではない、ということです。

 例年こんな形で豚汁会を行ってきましたが、今年は3歳未満の赤さんがいます。赤さんには野菜運びをやってもらうことにしました。 赤ちゃんを卒業したばかりの赤さんとは言え、ちゃんと手順は覚えました。

 さあ当日、園長先生は例年通り「豚汁準備始め」と号令をかけ、青さんから赤さんまでいっせいに自分の仕事をやり始めました。 そう、ここでも園長先生は赤さんがいるいないにかかわらず、例年通りの命令だけかければいいのです。

 長くなりましたが、ここからクラスの継承とポリモーフィズムに話は移っていきます。

スーパーすたぁサブちゃん?

 さあ、ここで保育園児と青さんの関係を考えてみましょう。

「青さんは保育園児です。」当たり前ですね。 でも「保育園児は青さんです。」は間違えです。青さん以外にも、黄さんも桃さんもいますから。 青さんをより一般的にしたものが保育園児というわけです。

次は保育園児や青さんをクラスにしてみましょう。

 プログラムにするわけではないので、クラス名も「保育園児」クラスや「青さん」クラスのままで行きましょう。

 ここで思い出してほしいのは、クラスはオブジェクト製造機だということです。 「青さん」クラスは、青さんの子供そのものではなく、青さんを生み出す機械なのです。 このことを頭に入れて次に進みましょう。

 保育園児や青さんをクラスとして考えたとき、より一般化した「保育園児」クラスをスーパークラス、 それを特殊化した「青さん」クラスをサブクラスといいます。

 サブクラスはスーパークラスを継承しているといいます。 つまり「青さん」クラスは「保育園児」クラスを継承しているわけです。

 クラスの関係を図にしてみましょう。クラスは一番左のような四角形で表します。 しつこいようですが、これは今までの説明の中で、顔の付いた長方形や、2辺の長さの入った四角や、太郎や5歳と入った四角とは違います。 あれはオブジェクトでしたが、こんどの四角はクラスです。



 青さんのクラスを考えて見ましょう。 クラス名は「青さん」、インスタンス変数には「氏名」、メソッドに「豚汁の準備をする」があるはずです。 ここでは見えませんが、このメソッドの中身は、野菜を切る、となっていることでしょう。 真ん中のようになります。

 この四角形はクラスですから、青さんの子供そのものでなく、青さんを生み出す機械なのです。 だからこの四角形を見て分かるのは、青さんクラスから作られたオブジェクトは、すべて氏名を持っていて、 (どんな方法かは別にして)豚汁の準備ができる、ということなのです。

クラスの関係を整理するときわかりやすいので、これからもこの四角を使っていこうと思います。


 さて保育園児というクラスを考えましょう。

 インスタンス変数には氏名を持っていて、園長先生の「豚汁の準備をしましょう」で動き出すのですから、 メソッド「豚汁の準備をする」があるはずです。 スーパークラスとサブクラスの関係は、線で結び片側を中抜き三角矢印にします。 矢印はサブクラスからスーパークラスへ向かいます。 この方向を間違えないように。 黄や桃も入れて図にしてみました。

これはUML(Unified Modeling Language)のクラス図に当たります。



 「青さん」クラスから、氏名が消えていますね。 実はサブクラスはスーパークラスの財産、つまりインスタンス変数やメソッドを受け継ぐので、 「保育園児」クラスで氏名をインスタンス変数に持っていれば、「青さん」クラスでは持つ必要はないのです。

 またメソッド「ラジオ体操をする」はスーパークラスの「保育園児」クラスに定義されているので、 青さん、黄さん、桃さんどのクラスでも同じ内容のものが定義されていると考えてよいのです。 同じ体操を何も組別に教える必要もないですよね。

 このようにサブクラスが共通して持つデータ(インスタンス変数)やメソッドは、スーパークラスでまとめて定義しておけば済みます。 一方サブクラスでは、そのクラス独自の部分に集中して作れます。

 同様に「豚汁の準備をする」メソッドも考えればよいのですが、これは青さん、黄さん、桃さんでやるべきことが違います。 当然その定義内容が違ってきますので、各クラスでそれぞれ図の中の青字のように定義しなおしてやる必要があります。 このようなやり方をメソッドのオーバーライド(override)といいます。

 オーバーライドによって同じメソッド呼び出しでも、相手が青さんか、黄さんか、桃さんかによって、 そのオブジェクトのやることを変えることが可能になります。

 overrideは「~に重なる」という意味で、スーパークラスのメソッド定義を、 まったく同じシグネチャ、同じ戻り値の型で、サブクラス側で定義しなおしてやります。 すると、同じメソッド呼び出しでスーパークラスと別の処理を行うことが可能になります。

 青さんクラスにはもうひとつ「給食当番をする」というメソッドがあります。 このように独自のメソッドを定義することも可能です。

 ではプログラムもどきを作ってみましょう。
 まずは「保育園児」クラスから。

public class 保育園児 {
 String 氏名;

 public void 豚汁の準備をする( ) {
 }
 public void ラジオ体操をする( ) {
  ラジオ体操のやり方
 }
}

 次は「青さん」クラスです。

public class 青さん extends 保育園児{
 public void 豚汁の準備をする( ) {
  野菜を切る←ここが黄さん、桃さんでは変わる!
 }

 public void 給食当番をする( ){←これは黄、桃さんにはない
  給食当番のやり方
 }
}

 ここで注目してほしいのは、「extends 保育園児」の部分です。
サブクラスでは、必ずスーパークラスの名前をここで指定してやる必要があります。

十把一絡げ

 今度は園長先生側を考えましょう。 今までどおりクラスMainの中のメソッドmainで、以下のことは行われていると考えましょう。

 実はスーパークラスの変数に、サブクラスのオブジェクト(つまりサブクラスで製造されたオブジェクト)を代入することが可能なのです。 つまり「保育園児」クラスの変数を用意しておけば、そこに「青さん」クラスでnewしたオブジェクトを代入できるのです。

 こんなかんじ。かんじなのであって本当のプログラムではありません。

保育園児 x;
x=new 青さん();

「保育園児」クラスの変数xに、青さんのオブジェクトの参照が代入されました。 スーパークラスの財産は「青さん」クラスでも使えるので

x.氏名 = “大木太郎”;
x.ラジオ体操をする();

とできます。

x.豚汁の準備をする();

の呼び出しで、xが参照するオブジェクト、つまり大木太郎くんのオブジェクトのメソッド「豚汁の準備をする」が呼び出されます。 太郎くんは青さんなので、青さんで定義された、野菜を切る、という行動に出ます。

x=new 黄さん();
x.氏名="山田花子";
x.豚汁の準備をする();

 今度はxは黄さんのオブジェクトを参照しているので、花子さんは黄さんで定義された、野菜の皮をむく、という行動に出ます。 ここで注目してほしいのは、同じ「x.豚汁の準備をする( );」という命令で、 xが参照するオブジェクトによって別の行動を起こす、ということなのです。

 これでは今ひとつ便利さがわからないと思いますので、配列にいろいろなオブジェクトが入っている場合を想定しましょう。

 保育園児のデータベースから、一人ずつ「氏名、組」といったデータを入力し、青黄桃のどれかのオブジェクトに変換し、 配列に詰め込む、というイメージです。 配列要素には、3個のサブクラスのオブジェクトのどれかの参照が入っています。 青さんか黄さんか桃さんかは、データを入力するまでわからないのです。

保育園児[] x=new 保育園児[20];
  *ここでx[0]~x[19]に、青さん、黄さん、桃さんのどれかのオブジェクトの参照が入る
for(int i = 0; i < x.length; i++) {
 x[i].豚汁の準備をする();
}

 繰り返しの中で、一々x[i]が青さん、黄さん、桃さんのどのオブジェクトを参照しているかをチェックする必要がないわけです。 (instanceofという演算子を使えば、チェックすることができます。)

 このようにクラスの継承を利用すると、サブクラスのオブジェクトを十把一絡げに命令できて便利です。 このような関係をポリモーフィズムといいます。

赤さんはどうすれば・・・

 さて今年はここに赤さんを追加してみましょう。

 赤さんというサブクラスを定義して、その中でメソッド「豚汁の準備をする」を、野菜を運ぶ、として定義すればよいのです。

public class 赤さん extends 保育園児 {
 public void 豚汁の準備をする( ) {
  野菜を運ぶ
 }
}

 命令する側、つまりメソッドmainで入力されるデータに赤さんが入ってきても、 肝心の豚汁作りの命令部分はなんら変更の必要はないのです。

「x[i].豚汁の準備をする();」のところです。これで緑さんが増えても、白さんが増えても同じようにすれば大丈夫。 変更に強いプログラムになりました。



オブジェクトを作らないクラスって?

 保育園児は、必ず青さんか黄さん、桃さん、赤さんのどれかになります。 ということは、「保育園児」というクラスからはオブジェクトを作る必要はないわけです。 そんな場合には抽象クラスを使います。

 間違って「保育園児」クラスからオブジェクトを生成するのを防げます。抽象クラスは定義の最初にabstractをつけます。

 またどのサブクラスでもメソッド「豚汁の準備をする」が必ずあり、スーパークラス「保育園児」クラスのものをオーバーライドしています。 つまり「保育園児」クラスのメソッド「豚汁の準備をする」は別に実体はなくてもかまわない、というわけです。 このようなメソッドは抽象メソッドにしておきます。

 抽象メソッドは必ずサブクラスでオーバーライドされなければなりません。

「保育園児」クラスを抽象クラスにしてみます。

public abstract class 保育園児 {
 String 氏名;
 public abstract void 豚汁の準備をする( ) ;
 public void ラジオ体操をする( ) {
  ラジオ体操のやり方
 }
}

 抽象メソッドは中身がないので{ }の部分は指定しません。すぐにセミコロン;があるのに注意しましょう。

 では一応プログラムを載せておきましょう。

[Hoikuenji.java]
public abstract class Hoikuenji {
 public String name;
 public void radio() {
  System.out.println("ラジオ体操をする");
 }

 public abstract void tonjiru();
}
[Aosan.java]
public class Aosan extends Hoikuenji {
 public void tonjiru() {
  System.out.println("野菜を切る");
 }

 public void lunch() {
  System.out.println("給食当番をする");
 }
}

[Kisan.java]
public class Kisan extends Hoikuenji {
 public void tonjiru() {
  System.out.println("野菜の皮をむく");
 }
}

[Momosan.java]
public class Momosan extends Hoikuenji{
 public void tonjiru() {
  System.out.println("野菜を洗う");
 }
}

[Akasan.java]
public class Akasan extends Hoikuenji {
 public void tonjiru() {
  System.out.println("野菜を運ぶ");
 }
}

[Main.java]
public class Main {
 public static void main(String[] args) {
  Hoikuenji[] x = new Hoikuenji[4];
  x[0] = new Aosan();
  x[0].name = "太郎";
  x[1] = new Kisan();
  x[1].name = "次郎";
  x[2] = new Momosan();
  x[2].name = "花子";
  x[3] = new Akasan();
  x[3].name = "三郎";
  for (int i = 0; i < x.length; i++) {
   System.out.println(x[i].name);
   x[i].radio();
   x[i].tonjiru();
  }
 }
}

 次のように出力されます。

太郎
ラジオ体操をする
野菜を切る
次郎
ラジオ体操をする
野菜の皮をむく
花子
ラジオ体操をする
野菜を洗う
三郎
ラジオ体操をする
野菜を運ぶ