Javaのデザインパターンを整理(構造編)
Javaのデザインパターンについて復習します。プログラムをデザインするにあたって、参考にできる引き出しは多く持っておきたいです。
デザインパターンとは
過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したもの
有名なのはGoFの23のデザインパターンです。それぞれのデザインパターンは3つの分類に分かれています。
- オブジェクトの「生成」に関するパターン
- プログラムの「構造」に関するパターン
- オブジェクトの「振る舞い」に関するパターン
構造に関するパターン
パターン名 | 概要 |
---|---|
Adapter | 元々関連性のない2つのクラスを接続するクラスを作る |
Composite | 再帰的な構造を表現する |
Bridge | クラスなどの実装と、呼出し側の間の橋渡しをするクラスを用意し、実装を隠蔽する |
Decorator | あるインスタンスに対し、動的に付加機能を追加する。Filterとも呼ばれる |
Facade | 複数のサブシステムの窓口となる共通のインタフェースを提供する |
Flyweight | 多数のインスタンスを共有し、インスタンスの構築のための負荷を減らす |
Proxy | 共通のインタフェースを持つインスタンスを内包し、利用者からのアクセスを代理する。Wrapperとも呼ばれる |
Adapterパターン
- 概要
- インターフェースに互換性のないクラスどおしを組み合わせることを目的としています。
- 既存システムと新システムのインターフェースの違いを吸収するAdaptorを用いることで、少ない変更で既存システムを新システムに適応することができます。
- Adaptorパターンの実装は、継承によるものと、委譲によるものと、2種類存在します。
- 何が嬉しいか
- 既存のクラスを変更することなく、別インターフェースで処理を呼び出すことができます。
継承によるAdaptor
クラス図
実装例
ProductAdapterクラスが既存のProductクラスのメソッドをラップしています。
interface ProductPrice { public int getPrice(); } class Product { private int cost; public int getCost() { return cost; } } class ProductAdapter extends Product implements ProductPrice { public int getPrice() { return this.getCost(); } }
委譲によるAdaptor
クラス図
実装例
abstract class ProductPrice { abstract int getPrice(); } class Product { private int cost; public int getCost() { return cost; } } class ProductAdapter extends ProductPrice { private Product product; public ProductAdapter() { this.product = new Product(); } @Override public int getPrice() { return product.getCost(); } }
Compositeパターン
クラス図
このパターンは実装例と合わせてクラス図を見ると分かりやすいかな...と思っています。
実装例
Component
public interface Entry { public void find(int depth); void add(Entry entry); void remove(); }
Composite
import java.util.ArrayList; import java.util.List; public class Directory implements Entry { private String name; private List<Entry> list; public Directory(String name) { this.name = name; list = new ArrayList<>(); } @Override public void find(int depth) { for (int i = 0; i < depth; i++) System.out.print(" "); System.out.printf("dir :%s\n", this.name); for (Entry entry : list) { entry.find(depth + 1); } } @Override public void add(Entry entry) { list.add(entry); } @Override public void remove() { for (Entry entry : list) { entry.remove(); } System.out.printf("directory [%s] removed.\n", this.name); } }
public class File implements Entry { private String name; public File(String name) { this.name = name; } @Override public void find(int depth) { for (int i = 0; i < depth; i++) System.out.print(" "); System.out.printf("file :%s\n", this.name); } @Override public void add(Entry entry) { throw new UnsupportedOperationException(); } @Override public void remove() { System.out.printf("%s removed.\n", this.name); } }
client
public class Main { public static void main(String[] args) { File file1 = new File("file1"); File file2 = new File("file2"); Directory dir1 = new Directory("dir1"); dir1.add(file1); Directory dir2 = new Directory("dir2"); dir1.add(dir2); dir2.add(file2); dir1.find(0); } }
結果
dir :dir1 file :file1 dir :dir2 file :file2
なお、client側で親子関係が循環した場合は、Component#operation()
を実行すると無限ループになります。つまり以下のようの実装すると無限ループになります。
public class Main { public static void main(String[] args) { Directory dir3 = new Directory("dir3"); dir3.add(dir3); dir3.find(0); } }
参考
Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで
- 作者: 谷本心,阪本雄一郎,岡田拓也,秋葉誠,村田賢一郎,アクロクエストテクノロジー株式会社
- 出版社/メーカー: 技術評論社
- 発売日: 2017/04/18
- メディア: 大型本
- この商品を含むブログを見る