オブジェクト指向プログラミング


 

委譲

1 委譲

委譲とは、あるオブジェクトの操作の一部を他のオブジェクトに処理させることです。クラス継承とほぼ同様の効果を得ることができます。

委譲を行なうオブジェクトは委譲先オブジェクトのアドレスを持ち、必要に応じてそのアドレスを切り替える事によって委譲先を変更し、動作にバリエーションを持たせることもできます。



図1 消防車と合体した救急車(継承)

例えば「消防救急車」を考えて見ましょう。

119番に電話しても、あわてていると救急なのか火事なのかをなかなか告げられないということがあるようです。そこで、救急でも消防でもどちらの機能も併せ持ち、どちらにも対応できるように「救急車」と「消防車」が合体した「消防救急車」を作ってしまおうということです。(図1)

オブジェクト指向でいえば、「消防車」と「救急車」の2つのクラスを継承して新しいクラスを作ることになります。これを「多重継承」というのですが、残念ながら C++ 以外では、複数のクラスを継承することはできません。


図2 消防車を牽引した救急車(委譲)

そこで、仕方がありません。「救急車」クラスだけを継承して新しいクラスを作り、火を消すことは「消防車」にお願いすることにします。「救急車」と「消防車」が合体しているのではなく、「救急車」が「消防車」を牽引していて火事ならば「消防車」が活躍するイメージです。(図2)

「救急車」にはない、消防という機能は「消防車」に委譲するわけです。



それでは FireAmbulance クラスとして作成してみましょう。

FireEngine.java
public class FireEngine extends Car { public FireEngine() { super("消防車"); } public void work() { //(2) System.out.println("火事を消す。"); } }
Ambulance.java
public class Ambulance extends Car { public Ambulance() { super("救急車"); } public void siren() { //(3) System.out.println("ピーポーピーポー"); } }
FireAmbulance.java
public class FireAmbulance extends Ambulance { private FireEngine fir = new FireEngine(); //(1) public void work() { //(2) fir.work(); } }

それでは、この FireAmbulance クラスのインスタンスを生成し、siren と run メソッドを呼び出すクラスも作成してみましょう。

Main.java
public class Main { public static void main(String[] args) { FireAmbulance fam = new FireAmbulance(); //(1) fam.work(); //(2) fam.siren(); //(3) fam.run(); //(4) } }
  1. 〈宣言/定義〉
  2. Ambulance を「基底クラス」に持つ「派生クラス」 FireAmbulance を宣言します。
  3. 生成されたオブジェクトを保持する FireEngine 型の変数を定義します。
  4. work メソッドを定義し、FireEngine オブジェクトの work を呼ぶようにします。
  5. 〈処理の流れ〉
  6. 「派生クラス」 FireAmbulance のオブジェクト変数 fam を宣言生成します。FireAmbulance のオブジェクトが生成されるとき、FireEngine のオブジェクトも生成され fir に保存されます。
  7. メソッド work を呼び出します。FireAmbulance の work メソッドが実行されます。そして、fir 内に保持している FireEngine オブジェクトの work メソッドが呼び出されます。
  8. メソッド siren を呼び出します「基底クラス」 Ambulance の siren メソッドが実行されます。
  9. メソッド run を呼び出します。Car の run メソッドが実行されます。

実行結果です。

火事を消す。
ピーポーピーポー
救急車が走る。
    



次に、また別の「救急車」を考えて見ましょう。


図3 ブルドーザーを牽引したブル救急車

災害のときは、道路に障害物があって救急車がなかなか思うように走れないこともあります。そのときは、ブルドーザーの力を借りて障害物をどけることのできる「ブル救急車」も必要です。BullAmbulance クラスとして作成してみましょう。


FireAmbulance クラスと同じように作成するだけです。ただクラス名を BullAmbulance にし、生成するオブジェクトを FireEngine でなく、Bulldozer にすれば出来上がりです。



BullAmbulance.java
public class BullAmbulance extends Ambulance { private Bulldozer bul = new Bulldozer(); public void work() { bul.work(); } }

2 ポリモーフィズム

図4 牽引する自動車が決まっている救急車

委譲を利用すれば、「消防救急車」や「ブル救急車」など、必要なときに必要な「救急車」をどんどん作って、いろいろな「救急車」を簡単にたくさん作ることができます。しかし、そのうち車庫に収まらなくなったり、点検や保守に手が回らなくなり放置されることになって、一回しか使わなかった「救急車」や、どう使っていいか分からない「救急車」でいっぱいになってしまいます。(図4)

つまり、同じようなのだけれども少しずつ機能の異なるクラスがたくさんできてしまうということです。オブジェクト指向では、新しい機能を持つクラスを簡単に追加できます。しかし、簡単に作れるからと言って、どんどん作っていってしまっては、保守が大変になるばかりです。


図5 牽引する自動車を変えられる救急車

それでは、もう一度考えて見ましょう。本来、これらの「救急車」は「消防車」や「ブルドーザー」と合体しているわけではなく牽引しているわけですから、それぞれに「救急車」を用意しなくても、「救急車」は1台だけで、牽引している自動車を換えるだけでよいはずです。

「消防車」を牽引した「消防救急車」も、「ブルドーザー」を牽引した「ブル救急車」も要りません。いろいろなものを牽引できる「スーパー救急車」さえあればよいのです。(図5)

「消防救急車」は「消防車」を、「ブル救急車」「ブルドーザー」を牽引するということが決まっていました。しかし、今度はどんな自動車でも牽引できる「スーパー救急車」を SuperAmbulance クラスとして作成してみましょう。


Car 型の変数には、Car オブジェクトか、Car の「派生クラス」のオブジェクトなら代入できます。SuperAmbulance では、Car 型の変数 car に、その「派生クラス」の FireEngine オブジェクトを代入しています。

こうすることによって、Car の「派生クラス」のオブジェクトなら同じように処理できるわけです。ここでは、Car クラスの持つ work メソッドを呼び出しています。しかし、これによって呼び出されるメソッドは、Car クラスの work メソッドではありません。変数 car に代入されているオブジェクトのクラスである FireEngine の work メソッドです。

ただし、こうした呼び出しで使用できるメソッドは、あくまで Car クラスに存在するものだけです。FireEngine クラスに独自に追加されたメソッドは呼び出すことができません。

基底クラス型に派生クラス型を代入することはオブジェクト指向の重要な特徴です。基本クラスのオブジェクト変数に異なる派生クラスのオブジェクトを代入することで、メソッドの働きが変わることを「ポリモーフィズム(多態性)」と呼びます。


Car.java
public class Car { protected String kind = "自動車"; public Car() { } public Car(String kind) { this.kind = kind; } public void run() { System.out.println(kind + "が走る。"); } public void work() { System.out.println(""); } }
FireEngine.java
public class FireEngine extends Car { public FireEngine() { super("消防車"); } public void work() { //(3) System.out.println("火事を消す。"); } }
Ambulance.java
public class Ambulance extends Car { public Ambulance() { super("救急車"); } public void siren() { //(4) System.out.println("ピーポーピーポー"); } }
SuperAmbulance.java
public class SuperAmbulance extends Ambulance { private Car car; public SuperAmbulance(Car car) { this.car = car; //(2) } public void work() { //(3) car.work(); } }
Main.java
public class Main { public static void main(String[] args) { SuperAmbulance sam = new SuperAmbulance(new FireEngine()); //(1) sam.work(); //(3) sam.siren(); //(4) sam.run(); //(5) } }
  1. 〈宣言/定義〉
  2. Ambulance を基底クラスに持つ派生クラス SuperAmbulance を宣言します。
  3. 生成されたオブジェクトを保持する Car 型の変数を定義します。
  4. work メソッドを定義し、与えられたオブジェクトの work を呼ぶようにします。
  5. 〈処理の流れ〉
  6. 派生クラス SuperAmbulance 型の変数 sam を宣言FireEngine のインスタンスを引数に SuperAmbulance のインスタンスを生成します。
  7. SuperAmbulance のオブジェクト生成時、コンストラクタがが呼ばれ、FireEngine のオブジェクトが car に保存されます。
  8. メソッド work を呼び出しますSuperAmbulance の work が実行されます。そして、car 内に保持している FireEngine オブジェクトの work メソッドが呼び出されます。
  9. メソッド siren を呼び出します基底クラス Ambulance の siren が実行されます。
  10. メソッド run を呼び出します。Car の run が実行されます。

実行結果です。

火事を消す。
ピーポーピーポー
救急車が走る。