はじめに
自己紹介
皆さん、こんにちは、Udemy講師の斉藤賢哉です。私はこれまで、25年以上に渡って企業システムの開発に携わってきました。特にアーキテクトとして、ミッションクリティカルなシステムの技術設計や、Javaフレームワーク開発などの豊富な経験を有しています。
様々なセミナーでの登壇や雑誌への技術記事寄稿の実績があり、また以下のような書籍も執筆しています。
いずれもJava EE(Jakarta EE)を中心にした企業システム開発のための書籍です。中でも 「アプリケーションアーキテクチャ設計パターン」は、(Javaに限定されない)比較的普遍的なテーマを扱っており、内容的にはまだまだ陳腐化していないため、興味のある方は是非手に取っていただけると幸いです(中級者向け)。
Udemy講座のご紹介
この記事の内容は、私が講師を務めるUdemy講座『Java Basic編』の一部の範囲をカバーしたものです。『Java Basic編』はこちらのリンクから購入できます(セールス対象外のためいつも同じ価格)。また定価の約30%OFFで購入可能なクーポンをQiita内で定期的に発行していますので、興味のある方は、ぜひ私の他の記事をチェックしてみてください。
この講座は、以下のような皆様にお薦めします。
- Javaの言語仕様や文法を正しく理解すると同時に、現場での実践的なスキル習得を目指している方
- 新卒でIT企業に入社、またはIT部門に配属になった、新米システムエンジニアの方
- 長年IT部門で活躍されてきた中堅層の方で、学び直し(リスキル)に挑戦しようとしている方
- 今後、フリーランスエンジニアとしてのキャリアを検討している方
- 「Chat GPT」のエンジニアリングへの活用に興味のある方
- 「Oracle認定Javaプログラマ」の資格取得を目指している方
- IT企業やIT部門の教育研修部門において、新人研修やリスキルのためのオンライン教材をお探しの方
この記事を含むシリーズ全体像
この記事はJava SEの一部の機能・仕様を取り上げたものですが、一連のシリーズになっており、シリーズ全体でJava SEを網羅しています。また認定資格である「Oracle認定Javaプログラマ」(Silver、Gold)の範囲もカバーしています。シリーズの全体像および「Oracle認定Javaプログラマ」の範囲との対応関係については、以下を参照ください。
チャプターの概要
このチャプターでは、継承と同じように、実装を再利用するための設計パターンである委譲について学びます。
11.2.1 委譲
委譲とは
委譲は、継承と同じように実装の再利用を目的としています。ただし委譲は、継承のようにJavaの言語としての仕組みというよりは、一種の設計パターンです。abstractやextendsといった、特定のキーワードを用いることもありません。
委譲では機能を委譲するクラスと、委譲されるクラスという2つのクラスが登場します。ここでは前者(機能を委譲する側)をFooクラス、後者(機能を委譲される側)をBarクラスとします。Fooクラスでは、Barクラスに機能を委譲するために、Barクラスへの参照をフィールドとして保持します。
このようなクラス間の委譲関係をクラス図で表すと、以下のようになります。
委譲はクラスの関係性においては「関連」に相当するため、実線で表します。
委譲の具体例
それではここで、委譲という設計パターンを、ECサイトの「顧客モデル」を用いて具体的に見ていきましょう。
まず機能を委譲される側です。DelegatedCustomerクラスのコードを示します。
public class DelegatedCustomer {
protected int id; // ID
protected String name; // 名前
protected int point; // ポイント
// コンストラクタ
public DelegatedCustomer(String name, int point) {
this.name = name;
this.point = point;
}
// アクセサメソッド
........
// 購入金額の上限をチェックする(共通実装)
public boolean overTotalPrice(int totalPrice) {
if (1_000_000 < totalPrice) {
return true;
}
return false;
}
}
このクラスは通常の具象クラスで、委譲によって再利用されることを目的に顧客として共通的な実装を提供します。
次にDelegatedCustomerクラスに機能を委譲する側です。一般会員を表すGeneralCustomerクラスのコードを示します。
public class GeneralCustomer {
//【1】委譲先インスタンスを保持するフィールド
private DelegatedCustomer delegatedCustomer;
//【2】コンストラクタ
public GeneralCustomer(String name, int point) {
delegatedCustomer = new DelegatedCustomer(name, point);
}
// アクセサメソッド
public int getId() {
return delegatedCustomer.getId();
}
public void setId(int id) {
delegatedCustomer.setId(id);
}
....その他のアクセサメソッドも同様に委譲する....
//【3】購入金額の上限をチェックする
public boolean overTotalPrice(int totalPrice) {
return delegatedCustomer.overTotalPrice(totalPrice);
}
//【4】ポイントを加算する
public void addPoint(int totalPrice) {
int point = (int) (totalPrice * 0.05); // 購入金額の5%
setPoint(getPoint() + point);
}
}
このクラスでは、DelegatedCustomerクラスに機能を委譲するために、delegatedCustomerフィールドとして保持【1】し、コンストラクタで初期化します【2】。
そして各メソッドの実装を見ると、delegatedCustomerフィールドを起点に、DelegatedCustomerクラスの同じ名前のメソッドを呼び出しているのが分かります。この点が、委譲という設計パターンの最も特徴的な部分です。例えば購入金額の上限チェックをするためのoverTotalPrice()メソッド【3】を見ると、このクラスで何らかの処理をするわけではなく、DelegatedCustomerクラスに処理を委譲し、その結果をそのまま返しています。
逆にポイントを加算するためのaddPoint()メソッド【4】は、一般会員固有の処理となるので、委譲することなくこのメソッドで処理を完結しています。
このように委譲という設計パターンでは、独自の処理は自身で行うものの、共通的な処理はフィールドを経由して他クラスに処理を委譲することで、実装の再利用を可能にします。
次にこの例に、ゴールド会員を表すGoldCustomerクラスを追加してみましょう。GoldCustomerクラスは、GeneralCustomerクラスとほとんど同じような実装になりますが、ポイント加算処理はゴールド会員固有の処理となるため、以下のように実装します。
// ポイントを加算する
public void addPoint(int totalPrice) {
int point = (int) (totalPrice * 0.1); // 購入金額の10%
setPoint(getPoint() + point);
}
継承か委譲か
前項で説明したように、委譲の設計パターンを利用すると、継承と同じように実装の再利用が可能になります。それでは、継承と委譲はどのように使い分けるべきでしょうか。
まず継承は、指針として以下のようなケースで採用します。
- 先に登場したECサイトの「顧客モデル」のように、クラス間の結び付きが強い場合
- クラスが同一のライブラリ内に所属していたり、同一のチームで開発されている場合
継承と委譲を比較すると継承の方が実装量は少なく、また実装の再利用だけではなく、多態性の恩恵に預かることができます。
ただし上記のようなケースに該当しない場合は、継承の利用は慎重に検討する必要があります。例えばJava SEのクラスライブラリや、他のチームで開発されたクラスをいたずらに継承してしまうと、親クラスの仕様が変更になった場合に思わぬ影響を受ける可能性があります。
このように親クラスとの距離感が比較的遠い場合には、委譲によってクラス間を緩やかに結び付けた方が、後々有利になるケースが多いでしょう。
このチャプターで学んだこと
このチャプターでは、以下のことを学びました。
- 委譲とは継承と同じように実装の再利用を目的とした設計パターンであること。
- 継承と委譲をどのように使い分けるべきか。