Adapterパターン
異なるインターフェースを持つオブジェクト同士の連携を可能にし、既存のコードの再利用を可能にする。
Adapter と呼ばれるクラスは、あるインターフェースを、クライアントが要求する別のインターフェースに変換する役割を持つ。
既存のコードが何らかの理由によって修正できない場合に有効(外部のライブラリを使用している、修正による影響範囲が不明で、テストのやり直しが面倒など)。
クラスアダプタ と オブジェクトアダプタ が存在する。
クラスアダプタを利用するには多重継承が必要なため、Javaでは実装することができない。
オブジェクトアダプタではコンポジションが利用される。
クラスアダプタ
Adapter
がTarget
やAdaptee
を継承することで実現。
オブジェクトアダプタ
Adapter
がAdaptee
を保持(コンポジション)することで実現
クラスアダプタの例
角度を表す単位には、「度」と「ラジアン」がある。
元々、三角形の角度を「度」によって求めるインターフェース(Triangle
)が存在し、そこへ新しく、「ラジアン」で求めるインターフェース(PizzaSlice
)が必要になったとする。
クライアント側はコードを変更したくないし、元々の「度」で角度を求めたいので、「ラジアン」で角度を求めるインターフェースを、Adapter(AngleAdapter
)によって適合させる必要がある。
別の言い方では、クライアントから見たときに、PizzaSlice
がTriangle
に見えなければならない。(例えがわかりづらくてすみません、たまたまラジアンを勉強したい時期だったので角度を題材にしました。)
Adapter(AngleAdapter
)は新しいインターフェース(PizzaSlice
)を保持することで内部的に利用する。
// Target (既存のインターフェース)
public interface Triangle {
// 角度を単位「度」で取得する
public double getAngleInDegree();
}
// Adaptee (既存のインターフェースに適合させたい、新たなインターフェース)
public class PizzaSlice {
// 1 radian は、半径が1の円の円周の長さなので、2π
private double angleInRadian = Math.PI;
// 角度を単位「radian」で取得する
public double getAngleInRadian() {
return angleInRadian;
}
}
// Adapter
public class AngleAdapter implements Triangle {
// Adaptee を保持する
PizzaSlice pizzaSlice;
public AngleAdapter(PizzaSlice pizzaSlice) {
this.pizzaSlice = pizzaSlice;
}
@Override
public double getAngleInDegree() {
// 保持している Adaptee を利用して、既存のインターフェース Triangle に適合させる
double radian = pizzaSlice.getAngleInRadian();
return 180 * radian / Math.PI;
}
}
public class Main {
public static void main(String[] args) {
// AngleAdapter によって、
// クライアントは既存のインターフェース(Triangle)を使って、
// 新しいインターフェース(PizzaSlice)を扱うことができる
Triangle triangle = new AngleAdapter(new PizzaSlice());
System.out.println("角度は" + triangle.getAngleInDegree() + "度です。");
}
}
Facadeパターン
Facade・・・ファサード。外観、正面。
複雑なシステム、ライブラリ、サブシステムを、外部に対してシンプルで統一的なインターフェースを提供するためのテクニック。
ただし、Facade が存在しても、サブシステムの高度な(低水準な)機能は依然として使うことができる。Facade はサブシステムをカプセル化しているのではなく、単純化された(高水準な)インターフェースを提供する。
Facade はサブシステムを隠蔽のためのカプセル化を目的としているのではなく、単純化されたインターフェースを提供することを目的としている。
最小知識の原則(Princple of Least Knowledge)
クラス間の依存関係を極力弱めることで、ソフトウェアコンポーネントの再利用性と保守性を向上させることを目的としたソフトウェアの設計原則のひとつ。
この原則に従うことにより、ソフトウェアコンポーネントは、より疎結合になり、変更や拡張が容易に行えるようになる。(ただし、あくまでも原則であって、利を得るため原則に従わない(トレードオフ)という判断も時に必要)
- オブジェクトは、友達を1人だけ持つべき。つまり、あるオブジェクトが他のオブジェクトと連携する際、できるだけ少ない数のオブジェクトと通信、依存することを心がける
- オブジェクトは、他のオブジェクトの内部構造や実装の詳細に関与すべきではない。オブジェクトは他のオブジェクトの公開されたインターフェースを使用して連携すべきであり、内部の実装やデータに直接アクセスすべきではないい
- オブジェクトは、他のオブジェクトについてできるだけ少なくの情報を持つべき。つまり、オブジェクトが他のオブジェクトの詳細情報を保持しすぎないようにするべき
似た概念としてデメテルの法則(Law of Demeter)がある。
Facadeパターンを使用しない場合
野菜炒めを作るためのプログラム。
// 肉
public class Meat {
public void cut() {
System.out.println("肉をカットします。");
}
public void add() {
System.out.println("野菜を加えます。");
}
}
// 野菜
public class Vegetable {
public void wash() {
System.out.println("野菜を洗います。");
}
public void cut() {
System.out.println("野菜をカットします。");
}
public void add() {
System.out.println("野菜を加えます。");
}
}
// 塩
public class Salt {
public void add() {
System.out.println("塩を加えます。");
}
}
// ソース
public class Sauce {
public void add() {
System.out.println("ソースを加えます。");
}
}
// クライアント
public class Main {
// 野菜炒めを作る(複雑な処理や、処理の順番をクライアント自らが制御している)
public static void main(String[] args) {
// 肉
Meat meat = new Meat();
meat.cut();
meat.add();
// 野菜
Vegetable vegetable = new Vegetable();
vegetable.wash();
vegetable.cut();
vegetable.add();
// 塩
Salt salt = new Salt();
salt.add();
salt.add(); // 塩を多めにする
// ソース
Sauce sauce = new Sauce();
sauce.add();
}
}
Facadeパターンを利用した場合
// Facade
public class CookFacade {
Meat meat;
Vegetable vegetable;
Salt salt;
Sauce sauce;
public CookFacade(Meat meat, Vegetable vegetable, Salt salt, Sauce sauce) {
this.meat = meat;
this.vegetable = vegetable;
this.meat = meat;
this.salt = salt;
this.sauce = sauce;
}
public void cook() {
// 肉
meat.cut();
meat.add();
// 野菜
vegetable.wash();
vegetable.cut();
vegetable.add();
// 塩
salt.add();
// ソース
sauce.add();
}
}
// クライアント
public class Main {
// 野菜炒めを作る(クライアントは複雑な実装の詳細を知らなくて済む)
public static void main(String[] args) {
CookFacade cookFacade = new CookFacade(
new Meat(), new Vegetable(), new Salt(), new Sauce()
);
cookFacade.cook(); // シンプルなインターフェース(高水準機能)
cookFacade.salt.add(); // 塩を多めにする(低水準機能も利用できる)
}
}