プログラミングにおいて良くつかわれるデザインパターンを以下に列挙します。
生成に関するパターン
- Factory Method パターン
- Abstract Factory パターン
- Builder パターン
- Prototype パターン
- Singleton パターン
構造に関するパターン
- Adapter パターン
- Bridge パターン
- Composite パターン
- Decorator パターン
- Facade パターン
- Flyweight パターン
- Proxy パターン
振る舞いに関するパターン
- Chain of Responsibility パターン
- Command パターン
- Interpreter パターン
- Iterator パターン
- Mediator パターン
- Memento パターン
- Observer パターン
- State パターン
- Strategy パターン
- Template Method パターン
- Visitor パターン
その他のパターン
- Model-View-Controller (MVC) パターン
- Model-View-Presenter (MVP) パターン
- Model-View-ViewModel (MVVM) パターン
これらのデザインパターンは、ソフトウェア設計における一般的な課題を解決するためのベストプラクティスとなっています。パターンを適切に使うことで、コードの保守性、拡張性、再利用性が高まります。
特に有名なデザインパターンとしては、Singletonパターン、Factoryパターン、Observerパターン、Decoratorパターン、MVCパターンなどがよく知られています。
プロジェクトの規模や要件に応じて、適切なデザインパターンを選択・適用することが重要です。単一のパターンのみを使うのではなく、場合によっては複数のパターンを組み合わせて使うことも一般的です。
Singletonパターン
Singletonパターンは、インスタンスを1つしか生成できないようにするデザインパターンです。Java Spring Frameworkでは、このSingletonパターンを利用して、Beanの生成を管理しています。
Singletonパターンの実装例
public class SingletonExample {
private static SingletonExample instance;
private SingletonExample() {}
public static SingletonExample getInstance() {
if (instance == null) {
instance = new SingletonExample();
}
return instance;
}
}
このクラスでは、以下の点に注目する必要があります。
- privateコンストラクタを持つため、外部からnewによるインスタンス生成ができない
- getInstanceメソッドが静的メソッドとなっており、instanceがnullの場合のみインスタンスを生成する
- 一度生成されたインスタンスが静的フィールドに保持される
これにより、クラスのインスタンスが1つしか存在しないことが保証されます。
Spring Frameworkでの活用
Spring Frameworkでは、Beanをデフォルトでsingletonスコープとして生成します。つまり、同一のBeanに対して毎回同じインスタンスが注入されます。
// AppConfig.java
@Configuration
public class AppConfig {
@Bean
public SingletonService singletonService() {
return new SingletonServiceImpl();
}
}
// SingletonServiceImpl.java
@Service
public class SingletonServiceImpl implements SingletonService {
// 実装
}
// Client.java
@Autowired
private SingletonService service;
AppConfigクラスでSingletonServiceBeanを定義しています。Clientクラスで@Autowiredされた際、同一のSingletonServiceImplインスタンスが常にDIコンテナから注入されます。
Spring FrameworkではSingletonスコープ以外にも、次のようなスコープが存在します。
- prototype: 毎回新しいインスタンスを生成する
- request: HTTPリクエストごとに新しいインスタンスを生成する(Webアプリケーション向け)
- session: HTTPセッションごとに新しいインスタンスを生成する(Webアプリケーション向け)
プロトタイプスコープは通常のnewによるインスタンス生成と同様ですが、リクエストスコープやセッションスコープはWebアプリケーションにおけるHTTP処理の単位に紐付いています。
メリットとデメリット
Singletonパターンのメリットは以下のようなことがあげられます。
- 一意のインスタンスが常に保証される
- グローバルアクセスポイントの提供が容易
- パフォーマンス上の利点(インスタンス生成コストが一度のみ)
一方でデメリットとしては、以下のようなことが考えられます。
- テスト時にモックが難しい
- 状態を保持するのでスレッドセーフでない可能性がある
- 継承できない場合がある(privateコンストラクタのため)
このように、Singletonには一長一短があるため、使用用途を見極める必要があります。Spring FrameworkではSingletonスコープは便利ですが、上記のデメリットも意識する必要があります。特にスレッド安全性の確保やテストコードの記述には注意が必要です。
Factoryパターン
Factoryパターンは、インスタンス生成のロジックをサブクラスに移譲することで、インスタンス生成の具体的な処理を呼び出し側に隠蔽するデザインパターンです。Java Spring Frameworkでもこのパターンが利用されています。
Factoryパターンの実装例
// 製品インターフェース
interface Product {
void operation();
}
// 製品の具体クラス
class ProductA implements Product {
public void operation() {
System.out.println("ProductA");
}
}
class ProductB implements Product {
public void operation() {
System.out.println("ProductB");
}
}
// Factoryクラス
class Factory {
public static Product getProduct(String type) {
if (type.equals("A")) {
return new ProductA();
} else if (type.equals("B")) {
return new ProductB();
}
return null;
}
}
// クライアント側
public class Client {
public static void main(String[] args) {
Product product = Factory.getProduct("A");
product.operation();
}
}
この例では、Factoryクラスが製品のインスタンス生成ロジックを担当しています。クライアントはFactoryのgetProductメソッドを呼ぶだけで、具体的な製品クラスを意識することなく、製品インスタンスを取得できます。
Spring FrameworkにおけるFactoryパターン
Spring FrameworkではBeanFactoryインターフェースとその実装クラスがFactoryパターンの役割を担っています。
// AppConfig.java
@Configuration
public class AppConfig {
@Bean
public ProductFactory productFactory() {
return new ProductFactoryImpl();
}
}
// ProductFactory.java
public interface ProductFactory {
Product createProduct();
}
// ProductFactoryImpl.java
@Component
public class ProductFactoryImpl implements ProductFactory {
@Override
public Product createProduct() {
return new ProductImpl();
}
}
// Client.java
@Autowired
private ProductFactory factory;
public void run() {
Product product = factory.createProduct();
product.operation();
}
この例では、ProductFactoryインターフェースを定義し、その実装であるProductFactoryImplがProductImplのインスタンスを生成しています。Clientクラスでは@Autowiredされた ProductFactoryから製品インスタンスを取得しています。
Factoryパターンのメリットは以下のようなことがあげられます。
- 製品の具体的な実装を隠蔽できる(カプセル化)
- 製品クラスの追加・変更が容易になる (オープン・クローズドの原則)
- サブクラスの責務分離ができる
一方でデメリットは以下のようなことが考えられます。
- 作成ロジックの複雑さに応じてコード量が増える可能性がある
- 静的メソッドによるケースでは、テスト時にモック作成が難しくなる
Spring FrameworkではBeanFactoryがファクトリメソッドのようなインスタンス生成を担当しています。特にBeanの生成方法を切り離すことで、Beanの具象クラスを気にすることなくインスタンス生成ができるというメリットがあります。
Factoryパターンは単独で使うよりも、他のデザインパターン(Strategyパターンなど)との組み合わせで効果を発揮することが多いパターンです。
Observerパターン
Observerパターンは、オブジェクトの状態変化を関係するオブジェクトに通知するデザインパターンです。1対多の依存関係を定義できるため、オブジェクト間の結合度を低く保つことができます。
Java Springフレームワークでは、Observerパターンの考え方が様々な場所で取り入れられています。代表的な例としては以下が挙げられます。
1. イベントハンドリングメカニズム
Springは自身のイベントハンドリングメカニズムを提供しており、そこでObserverパターンが利用されています。
// Observerインターフェース
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
// Subjectクラス
@Component
public class EmailService {
@Autowired
private ApplicationEventPublisher publisher;
public void sendEmail(String email, String body) {
// メール送信処理...
// イベント発行
publisher.publishEvent(new EmailSentEvent(this, email));
}
}
// ObserverクラスとするEventListener
@Component
public class EmailSentLogger implements ApplicationListener<EmailSentEvent> {
@Override
public void onApplicationEvent(EmailSentEvent event) {
String email = event.getEmail();
System.out.println("Email sent to: " + email);
}
}
この例では、EmailServiceがSubjectの役割を果たし、ApplicationEventPublisherを介してイベントを発行します。一方、EmailSentLoggerはObserverとなり、EmailSentEventが発生したタイミングでログ出力を行います。
ApplicationEventPublisherは、Spring内部でApplicationListenerの実装を検索し、該当するリスナーに対してイベントを送信するメカニズムを持っています。
2. Spring Data
Spring DataのリポジトリイベントPublicationでもObserverパターンが利用されています。
@Component
public class UserRegisteredListener implements ApplicationListener<UserRegisteredEvent> {
@Override
public void onApplicationEvent(UserRegisteredEvent event) {
User user = event.getUser();
// ユーザー登録イベントの処理
}
}
このように、リソースの作成、更新、削除イベントをアプリケーションで購読できます。
3. メッセージングとの連携
Spring IntegrationやSpring AMQPなどのメッセージングでもObserverパターンの考え方が生かされています。
Observerパターンのメリットは、Subject-Observerが1対多の関係になるため、Subject-Observerを明示的に結合する必要がなく、疎結合となることです。また、SubjectとObserverの役割分担によって、関心の分離が図れます。
一方、Observerパターンでは、Subjectの状態がどのタイミングで変更されるのかを事前に知ることは難しいというデメリットもあります。
このように、Observerパターンは様々な箇所で活用されており、Springもパターンのメリットをうまく生かしているといえます。ただし、パターンを適切に利用するためには、パブリッシュ-サブスクライブのメカニズムや、関係するオブジェクトの役割を理解する必要があります。