Bridge
「機能のクラス階層」と「実装のクラス階層」を分けて,これらのクラスを橋渡しする構造をとる.クラス構造を分離することで,継承が深くなったり,機能が重複したりすることを防ぐことができる.
「機能のクラス階層」では,上位のクラスに無い機能を追加するために,上位のクラスを継承して新しいクラスを構成する.スーパークラスは,基本的な機能を持っている.サブクラスでは,スーパークラスにない機能が追加される.すなわち,機能の追加を構成する階層である.
「実装のクラス階層」では,決められたAPIを使った処理内容について,多種にわたる実装があるときに,構成する階層である.スーパークラスでは,抽象メソッドとしてインターフェイスを定義している.サブクラスでは,具象メソッドとしてインターフェイスを実装する.
これらを分離しない場合には,機能のクラス階層に加えて,実装のクラス階層が継承される形になることが考えられる.
クラス構造
たとえば,自動車の種類をもとに階層化したとするとき,その動力に関する種類が多数あるとき,具体化された自動車それぞれに,動力の種類が割り当てられることになる.
見方を変えて,車種に分けてみると,今後は,車種ごとに動力の異なるクラスを設ける必要が出てくる.
このように,複数の要素を持つとき,階層が深くなる,機能が重複する,などの弊害が生じる.
そこで,「機能のクラス構造」として車の種類を構成し,「実装のクラス構造」として,動力となるエネルギーの種類を構成する.そのうえで,車がエネルギーとなる動力を持つというクラス内部構造にする.これにより,動力を適切に選ぶことができる.
ソースコード
車の種類について,車のタイプについて機能ごとに分け,実装の違いとして,動力の違いで分ける.各車のタイプそれぞれで,動力の違いをフィールドとして持つことで,関係づける.
実装のクラス
他方,「実装のクラス階層」として,動力を設定するための抽象クラスとして動作を定義する「Movement」クラスを定義する.
abstract public class Movement { abstract public void forward(); abstract public void back(); abstract public void turn(Boolean side); }
Movementクラスを継承して,実装するための具体的なクラスとして,「ガソリンクラス」(GasolineMovement)や「電気クラス」(MotorMovement)を定義する.それぞれ,動作のメソッドを実装しているが,内容はクラスごとに異なる.
public class GasolineMovement extends Movement { @Override public void forward() { System.out.println("ブォーン「前進します」"); } @Override public void back() { System.out.println("ブッフ「バックします」"); } @Override public void turn(Boolean side) { if(side) { System.out.println("シュッ「右に曲がります」"); } else { System.out.println("シュッ「左に曲がります」"); } } }
public class MotorMovement extends Movement { @Override public void forward() { System.out.println("シュウィーン「前進します」"); } @Override public void back() { System.out.println("シュッ「バックします」"); } @Override public void turn(Boolean side) { if(side) { System.out.println("シュッ「右に曲がります」"); } else { System.out.println("シュッ「左に曲がります」"); } } }
機能のクラス
抽象クラスとして定義する車クラス(Car)のコードに示す.車クラスは,車として継承するクラスで具体化する「排気量」メソッドを持つ.さらに,実装によって異なる,動力源を表すMovementクラスの動力フィールド(move)を持つ.これによって,「実装のクラス構造」を集約の関係でつながっている.
さらに,動作については,委譲を利用しており,前進やバックなどのメソッドでは,動力源を使っている.基本的な機能を車クラスは持っているが,それらの機能に加えて,追加することができる.
public class Car { private Movement move; Car(Movement move) { this.move = move; } public void forward() { move.forward(); } public void back() { move.back(); } public void turn(Boolean side) { move.turn(side); } }
車クラスを継承して,トラッククラス(Truck),バスクラス(Bus)を定義する.
トラッククラスやバスクラスでは,バックするときに,単な動作を遂行する他に,大型である故に周辺への注意喚起として,音声にて伝えることが見られないだろうか.これを追加する機能として実装するために,継承したクラスで,メソッドをオーバーライドしている.
public class Truck extends Car { Truck(Movement move) { super(move); } public void back() { System.out.print("ピーッピーッ"); super.back(); } }
public class Bus extends Car { Bus(Movement move) { super(move); } public void back() { System.out.print("「後ろに下がります.ご注意ください.」"); super.back(); } }
確認
動作を確認するために,実際にインスタンス化して,実行してみる. 自動車の動作については,動力源であるMovementに委譲しているので,各種車を作る時には,動力源を設定するようにしている.そこで,動力源をインスタンス化して,トラック,バスをインスタンス化している. 車の動作について,電気モータであっても,ガソリンエンジンであっても,同じメソッドで操作することができている様子が確認できる.動力源の違いや,機能の違いによって,表示が変化していることが分かるだろうか.public class Main { public static void main(String[] args) { System.out.println("-----電気トラックを作ります-----"); Car hino2t = new Truck(new MotorMovement()); hino2t.back(); hino2t.forward(); hino2t.turn(false); System.out.println("-----ガソリンバスを作ります-----"); Car isuzuBus = new Bus(new GasolineMovement()); isuzuBus.forward(); isuzuBus.turn(true); isuzuBus.back(); } }
-----電気トラックを作ります----- ピーッピーッシュッ「バックします」 シュウィーン「前進します」 シュッ「左に曲がります」 -----ガソリンバスを作ります----- ブォーン「前進します」 シュッ「右に曲がります」 「後ろに下がります.ご注意ください.」ブッフ「バックします」
CarクラスとMovementクラスが橋の両端といえるだろうか.さらに,その橋になるのが,Carクラスのフィールドとして定義されたMovementフィールドである.このフィールドが指すオブジェクトを介して,実際の動作を得ている.この仕組みは「委譲」である.
演習
(1)追加
車の種類として,軽自動車を追加する.さらに,水素による動力を追加する.そのうえで,水素軽自動車をインスタンス化する.
(2)ブリッジクラスの作成
消防車や救急車を想定した,サイレンをもつ車クラスを機能のクラスとし,サイレンを実装のクラスとする.サイレン車は,サイレンの実装をサイレンクラスに委譲するものとする.
応用課題
ハイブリッドな動力源を設定したい時には,どのように実装したらよいだろうか?
提出
(1)-(3)を含むPDF.