Javaにおけるサーヴァントパターン:サーヴァントヘルパーによる豊富なインタラクションの促進
別名
- ヘルパー
サーヴァントデザインパターンの意図
サーヴァントパターンは、操作対象となる要素のクラスを変更することなく、オブジェクトのグループに対して特定の操作を実行するために使用されます。
現実世界の例を用いたサーヴァントパターンの詳細な解説
現実世界の例
レストランでは、ウェイター(サーヴァント)が注文を受けたり、料理を運んだり、支払いを処理したりすることで、複数のテーブル(オブジェクト)に対応します。ウェイターは、テーブルや顧客の本質的な性質を変更することなく、これらの共通サービスを提供します。これにより、レストランはテーブルや顧客の役割を変えずに、効率的に顧客サービスを管理できます。
平易な言葉で
Javaサーヴァントパターンは、複数のクラスに対する共通機能を効果的に一元化し、元のクラスを変更することなく、ソフトウェア開発において疎結合で再利用可能な操作を可能にし、効率的なJava設計プラクティスを促進します。
Wikipediaによると
ソフトウェア工学において、サーヴァントパターンは、クラスのグループに機能を提供するために使用されるオブジェクトを定義します。この機能は、各クラスで定義する必要はありません。サーヴァントは、目的のサービスを提供するメソッドを持つクラス(または単にクラス)であり、サーヴァントが何かを行う対象となるオブジェクトは、パラメータとして渡されます。
Javaにおけるサーヴァントパターンのプログラミング例
サーヴァントデザインパターンは、クラスのグループに何らかのサービスを提供するクラスを定義する行動デザインパターンです。このパターンは、これらのクラスがスーパークラスに追加できない共通機能が不足している場合に特に役立ちます。サーヴァントクラスは、この共通機能をクラスのグループにもたらします。
提供されたコードでは、Servant
クラスがRoyalty
クラスにサービスを提供しています。Servant
クラスには、feed()
、giveWine()
、giveCompliments()
のようなメソッドがあり、これらはRoyalty
クラスに提供されるサービスです。
これがServant
クラスです
public class Servant {
public String name;
public Servant(String name) {
this.name = name;
}
public void feed(Royalty r) {
r.getFed();
}
public void giveWine(Royalty r) {
r.getDrink();
}
public void giveCompliments(Royalty r) {
r.receiveCompliments();
}
public boolean checkIfYouWillBeHanged(List<Royalty> tableGuests) {
return tableGuests.stream().allMatch(Royalty::getMood);
}
}
Royalty
クラスは、Servant
のサービスを使用するクラスによって実装されるインターフェースです。これがRoyalty
インターフェースの簡略化されたバージョンです
public interface Royalty {
void getFed();
void getDrink();
void receiveCompliments();
void changeMood();
boolean getMood();
}
App
クラスは、Servant
を使用してRoyalty
オブジェクトにサービスを提供します。これがApp
クラスの簡略化されたバージョンです
public class App {
private static final Servant jenkins = new Servant("Jenkins");
private static final Servant travis = new Servant("Travis");
public static void main(String[] args) {
scenario(jenkins, 1);
scenario(travis, 0);
}
public static void scenario(Servant servant, int compliment) {
var k = new King();
var q = new Queen();
var guests = List.of(k, q);
servant.feed(k);
servant.feed(q);
servant.giveWine(k);
servant.giveWine(q);
servant.giveCompliments(guests.get(compliment));
guests.forEach(Royalty::changeMood);
if (servant.checkIfYouWillBeHanged(guests)) {
LOGGER.info("{} will live another day", servant.name);
} else {
LOGGER.info("Poor {}. His days are numbered", servant.name);
}
}
}
アプリケーションを実行すると、次のようになります
09:10:38.795 [main] INFO com.iluwatar.servant.App -- Jenkins will live another day
09:10:38.797 [main] INFO com.iluwatar.servant.App -- Poor Travis. His days are numbered
この例では、Servant
クラスがRoyalty
オブジェクトにサービスを提供します。Servant
クラスは、Royalty
オブジェクトの具体的な実装については知らず、特定のサービスを提供できることだけを知っています。これは、サーヴァントデザインパターンの良い例です。
Javaでサーヴァントパターンを使用するタイミング
- クラス定義を汚染することなく、クラスのグループに共通機能を提供する必要がある場合は、サーヴァントパターンを使用します。Javaアプリケーションアーキテクチャを強化するのに最適です。
- オブジェクトに対する操作が、オブジェクト自身の主な責任ではない場合に適しています。
Javaにおけるサーヴァントパターンの現実世界での応用
- 異なるUIコンポーネントで共通のレンダリングやヒットテストなどの操作を処理するためのGUIアプリケーション。
- プレイヤー、敵、アイテムなどのさまざまなエンティティに、移動や衝突検出などの共通の動作が必要なゲーム。
- 複数のビジネスオブジェクトで必要なロギングや監査機能。
サーヴァントパターンの利点とトレードオフ
利点
- 操作対象のオブジェクトから操作を分離することで、コードの再利用と関心の分離を促進します。
- 共有機能を一元化することで、コードの重複を削減します。
トレードオフ
- クラスの数が増加し、システムが理解しにくくなる可能性があります。
- 注意深く設計しないと、サーヴァントとそのサービスを受けるクラスの間に強い結合が生じる可能性があります。
関連するJavaデザインパターン
- アダプター:サーヴァントパターンは、クラスを変更せずにクラスを操作する方法を提供するという点でアダプターパターンに似ていますが、サーヴァントパターンは、あるインターフェースを別のインターフェースに適合させるのではなく、複数のクラスに追加の動作を提供することに焦点を当てています。
- ファサード:どちらのパターンも、一連の機能に対して簡略化されたインターフェースを提供しますが、サーヴァントパターンは通常、クラスのグループに機能を追加するために使用され、ファサードパターンはサブシステムの複雑さを隠します。
- ストラテジー:ストラテジーパターンは、アルゴリズムのファミリーを定義し、それぞれをカプセル化し、それらを交換可能にします。サーヴァントパターンは、複数のクラスに適用する操作を定義するためにストラテジーパターンと組み合わせて使用できます。
- ビューヘルパー:ビューヘルパーパターンも共通機能を一元化するという点で関連していますが、Webアプリケーションにおけるプレゼンテーションロジックとビジネスロジックの分離に焦点を当てています。