単純工場パターン
- 定義
工場オブジェクトがどの製品クラスのインスタンスを作成するかを決定するパターン - 適用シーン
工場クラスが作成するオブジェクトの種類が比較的少ない場合
クライアント(アプリケーション層)は工場クラスに渡すパラメータのみを知っており、オブジェクト作成のロジックには関与しない場合 - メリット
正しいパラメータを渡すだけで必要なオブジェクトを取得でき、オブジェクト作成の詳細を意識する必要がない - デメリット
工場クラスの責任が過重になる
新製品の追加時に工場クラスの判断ロジックを修正する必要があり、開放・閉鎖の原則に違反する可能性がある
public abstract class DocumentBase {
public abstract void generateDocument();
}
-------------------------------------
public class HtmlDocument extends DocumentBase{
@Override
public void generateDocument() {
System.out.println("HTMLドキュメントを生成");
}
}
-------------------------------------
public class XmlDocument extends DocumentBase {
@Override
public void generateDocument() {
System.out.println("XMLドキュメントを生成");
}
}
--------------------------------------
public class DocumentFactory {
/**
* リフレクションを使用した単純工場パターン実装--開放・閉鎖の原則に準拠
* @param clazz クラス
* @return
*/
public DocumentBase createDocument(Class clazz){
DocumentBase document = null;
try {
document = (DocumentBase) Class.forName(clazz.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return document;
}
}
------------------------------------------
public class Demo {
public static void main(String[] args) {
DocumentFactory factory = new DocumentFactory();
DocumentBase doc = factory.createDocument(XmlDocument.class);
if (doc == null){
return;
}
doc.generateDocument();
}
}
工場メソッドパターン
- 定義
オブジェクト作成のためのインターフェースを定義し、そのインターフェースを実装するクラスがどのクラスをインスタンス化するかを決定するパターン。工場メソッドによりクラスのインスタンス化をサブクラスに延期する - 適用シーン
オブジェクト作成に多くの重複コードが必要な場合
クライアント(アプリケーション層)が製品クラスのインスタンスがどのように作成されるかなどの詳細に依存しない場合
クラスがそのサブクラスを介してどのオブジェクトを作成するかを指定する場合 - メリット
ユーザーは必要な製品に対応する工場クラスにのみ注目すればよく、作成の詳細を意識する必要がない
新製品の追加が開放・閉鎖の原則に準拠し、拡張性が向上する - デメリット
クラスの数が容易に増加し、システムの複雑さが増す
システムの抽象性と理解の難易度が増加する
抽象工場パターン
- 定義
一連の関連または相互依存するオブジェクトを作成するインターフェースを提供し、それらの具体的なクラスを指定しないパターン - 適用シーン
クライアント(アプリケーション層)が製品クラスのインスタンスがどのように作成されるか、実装などの詳細に依存しない場合
一連の関連製品オブジェクト(同じ製品族に属する)を一緒に使用することを強調する場合
オブジェクト作成に多くの重複コードが必要な場合
製品クラスのライブラリを提供し、すべての製品が同じインターフェースとして現れるようにすることで、クライアントが具体的な実装に依存しないようにする場合 - メリット
具体的な製品がアプリケーション層のコードから隔離され、作成の詳細を意識する必要がない
一連の製品族を統一して作成する - デメリット
すべての作成可能な製品の集合を規定しており、製品族に新しい製品を追加するのが困難で、抽象工場のインターフェースを修正する必要がある
システムの抽象性と理解の難易度が増加する
分かりやすい理解:
製品族:東芝のテレビ、東芝の冷蔵庫、東芝の洗濯機はすべて東芝ブランドに属し、一つの製品族である
製品階層構造:東芝のテレビ、シャープのテレビは同じ製品階層に属する
工場メソッドパターンは製品階層構造に対応し、抽象工場パターンは製品族に対応する
ビルダーパターン
- 定義
複雑なオブジェクトの構築とその表現を分離し、同じ構築プロセスで異なる表現を作成できるようにするパターン。ユーザーは構築するタイプを指定するだけで取得でき、構築プロセスと詳細を知る必要はない - 適用シーン
オブジェクトが非常に複雑な内部構造(多くの属性)を持つ場合
複雑なオブジェクトの作成と使用を分離したい場合 - メリット
カプセル化が良く、作成と使用が分離されている
拡張性が良く、ビルダークラス間が独立しており、ある程度の結合が解除されている - デメリット
余分なBuilderオブジェクトが生成される
製品内部に変更が生じると、ビルダーをすべて修正する必要があり、コストが大きい
public class Product {
private String productName;
private String productImage;
private String productPrice;
public Product(ProductBuilder productBuilder){
this.productName = productBuilder.productName;
this.productImage = productBuilder.productImage;
this.productPrice = productBuilder.productPrice;
}
public static class ProductBuilder{
private String productName;
private String productImage;
private String productPrice;
public ProductBuilder withName(String name){
this.productName = name;
return this;
}
public ProductBuilder withImage(String image){
this.productImage = image;
return this;
}
public ProductBuilder withPrice(String price){
this.productPrice = price;
return this;
}
public Product construct(){
return new Product(this);
}
}
}
---------------------------------------
public class Test {
public static void main(String[] args) {
Product product = new Product.ProductBuilder()
.withName("スマートフォン")
.withImage("スマートフォン画像")
.withPrice("99.99")
.construct();
System.out.println(product);
}
}
シングルトンパターン
- 定義
クラスがインスタンスを一つしか持たないことを保証し、グローバルなアクセスポイントを提供するパターン - 適用シーン
いかなる状況でも絶対に一つのインスタンスであることを保証したい場合 - メリット
メモリ内に一つのインスタンスしか存在しないため、メモリ使用量が削減される
リソースの多重占有を回避できる
グローバルアクセスポイントを設定し、アクセスを厳密に制御できる - デメリット
インターフェースがないため、拡張が困難 - 重要ポイント
プライベートコンストラクタ
スレッドセーフティ
遅延ロード
シリアライズとデシリアライズの安全性
リフレクション対策
/**
* 遅延初期化シングルトン--スレッドセーフではない--遅延ロードに重点
*/
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton(){}
/**
* synchronizedでスレッドセーフにする--staticを修飾する場合、ロックはクラス全体にかかる
*/
public synchronized static LazySingleton getInstance(){
if (instance == null){
//マルチスレッド実行の場合、複数回newされる可能性がある
instance = new LazySingleton();
}
return instance;
}
}
/**
* ダブルチェックとvolatileによるリオーダリング防止
*/
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton instance = null;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
if (instance == null){
synchronized (LazyDoubleCheckSingleton.class){
if (instance == null){
instance = new LazyDoubleCheckSingleton();
//リオーダリング問題
//1.オブジェクトにメモリを割り当て
// //3.instanceが割り当てられたメモリアドレスを指す
//2.オブジェクトを初期化
// intra-thread semantics
// -----//3.instanceが割り当てられたメモリアドレスを指す
}
}
}
return instance;
}
}
// 静的内部クラス
public class StaticInnerClassSingleton {
private static class InnerClass{
private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance(){
return InnerClass.instance;
}
private StaticInnerClassSingleton(){}
}
// 飢え鬼シングルトン--スレッドセーフ--クラスロード時に初期化完了
public class HungrySingleton {
private final static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
--------------静的コードブロック初期化-----------
public class HungrySingleton {
private final static HungrySingleton instance;
static{
//メリット-その他の初期化コードを実行可能
instance = new HungrySingleton();
}
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
プロトタイプパターン
- 定義
プロトタイプインスタンスが作成されるオブジェクトの種類を指定し、これらのプロトタイプをコピーすることで新しいオブジェクトを作成するパターン。作成の詳細を知る必要がなく、コンストラクタを呼び出さない - 適用シーン
クラスの初期化に多くのリソースを消費する場合
newでオブジェクトを生成する際に非常に煩雑なプロセス(データ準備、アクセス権限など)が必要な場合
コンストラクタが比較的複雑な場合
ループ内で大量のオブジェクトを生成する場合 - メリット
プロトタイプパターンの性能は直接newでオブジェクトを作成する性能よりも高い
作成プロセスを簡素化する - デメリット
クローンメソッドを必ず備える必要がある
複雑なオブジェクトのクローンやクローンされたオブジェクトに対する複雑な変更を行う場合、リスクが生じやすい
ディープコピー、シャローコピーを適切に使用する必要がある - 拡張
ディープクローン
シャロークローン
/**メールクラスがCloneableインターフェースを実装
*/
public class Email implements Cloneable{
private String recipientName;
private String emailAddress;
private String emailContent;
public Email(){
System.out.println("Email作成");
}
/*getter/setterメソッド*/
@Override
protected Object clone() throws CloneNotSupportedException {
System.out.println("Emailクローン");
return super.clone();
}
}
---------------------------
/**メール送信クラス
*/
public class EmailService {
public static void sendEmail(Email email){
String outputContent = "{0}様,アドレス:{1},内容:{2}へのメール送信完了";
System.out.println(MessageFormat.format(outputContent, email.getRecipientName(), email.getEmailAddress(), email.getEmailContent()));
}
public static void saveOriginalEmailRecord(Email email){
System.out.println("オリジナルレコード保存,originalEmail:" + email.getEmailContent());
}
}
-------------------------------
/**プロトタイプパターンテスト
*/
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Email email = new Email();
email.setEmailContent("初期化テンプレート");
System.out.println("初期化email:" + email);
for(int i = 0; i < 3; i++){
Email emailClone = (Email) email.clone();
emailClone.setRecipientName("名前" + i);
emailClone.setEmailAddress("" + i + "@example.com");
emailClone.setEmailContent("当選おめでとうございます");
EmailService.sendEmail(emailClone);
System.out.println("クローンされたemailClone:" + emailClone);
}
EmailService.saveOriginalEmailRecord(email);
}
}