1 はじめに
動作デザイン パターンは、オブジェクト間のコミュニケーションと対話に焦点を当てたデザイン パターンのクラスであり、オブジェクト間の責任の割り当てとコラボレーションの問題を解決するために使用されます。これらのパターンは、オブジェクトがどのように連携して単一のタスクを達成するか、およびオブジェクトの疎結合を維持する方法を記述します。
行動デザイン パターンは次のカテゴリに分類できます。
1. テンプレート メソッド パターン: サブクラスが 1 つ以上のステップの実装を提供できるようにするアルゴリズム フレームワークを定義します。
2. オブザーバー パターン (Observer Pattern) : オブジェクト間の 1 対多の依存関係を定義します。これにより、オブジェクトの状態が変化すると、それに依存するすべてのオブジェクトが通知され、自動的に更新されます。
3. 戦略パターン (Strategy Pattern) : 一連のアルゴリズムを定義し、それらを独立したクラスにカプセル化して相互に置き換えることにより、アルゴリズムの変更がアルゴリズムを使用するクライアントから独立するようにします。
4. コマンド パターン: リクエストをオブジェクトにカプセル化し、さまざまなリクエスト、キュー、またはログを使用して他のオブジェクトをパラメータ化できるようにします。コマンド モードでは、取り消し可能な操作もサポートされています。
5. 状態パターン (State Pattern) : 内部状態が変化したときにオブジェクトの動作を変更できるようにします。オブジェクトはそのクラスを変更したようです。
6. 責任連鎖パターン: リクエスト プロセッサーをチェーン構造で編成し、プロセッサーが処理するまで各リクエストを複数回処理できるようにします。
7. インタプリタ パターン: 言語を定義し、その言語表現を解析し、表現された操作を実行します。
8. Visitor Pattern (訪問者パターン) : 新しい操作を定義し、それをオブジェクトのセットに適用します。ビジター パターンは、オブジェクトのクラスから独立した操作を行うことができます。
2. テンプレートメソッドパターン
テンプレート メソッド パターンは、アルゴリズムのフレームワークを定義し、いくつかのステップの実装をサブクラスに延期する動作設計パターンです。これにより、さまざまなサブクラスが実際のニーズに応じてこれらのステップを実装できます。
テンプレート メソッド パターンでは、抽象クラスが定義されます。このクラスには、サブクラスによって実装できる 1 つ以上の抽象メソッドが含まれます。また、抽象メソッドを呼び出して特定のアルゴリズム フローを完了する具体的なテンプレート メソッドも含まれます。
簡単なサンプルコードを次に示します。
abstract class AbstractClass {
public void templateMethod() {
// step 1
operation1();
// step 2
operation2();
// step 3
operation3();
}
protected abstract void operation1();
protected abstract void operation2();
protected void operation3() {
// 默认实现,子类可以选择重写
}
}
class ConcreteClass1 extends AbstractClass {
@Override
protected void operation1() {
System.out.println("ConcreteClass1 operation1");
}
@Override
protected void operation2() {
System.out.println("ConcreteClass1 operation2");
}
}
class ConcreteClass2 extends AbstractClass {
@Override
protected void operation1() {
System.out.println("ConcreteClass2 operation1");
}
@Override
protected void operation2() {
System.out.println("ConcreteClass2 operation2");
}
@Override
protected void operation3() {
System.out.println("ConcreteClass2 operation3");
}
}
このサンプル コードでは、これは抽象クラスであり、 、、の 3 つのステップを含むAbstractClass
テンプレート メソッド を定義します。ここで、 と はサブクラスによって実装する必要がある抽象メソッドですが、デフォルトの実装メソッドであり、サブクラスはオーバーライドするかどうかを選択できます。templateMethod()
operation1()
operation2()
operation3()
operation1()
operation2()
operation3()
ConcreteClass1
と はConcreteClass2
それぞれ、AbstractClass
抽象メソッドを継承して実装します。このメソッドConcreteClass2
は overridden され、デフォルトの実装をオーバーライドします。operation3()
テンプレート メソッド パターンはフレームワークの設計でよく使用され、フレームワークはアルゴリズムのフレームワークを定義し、具体的な実装はサブクラスによって行われます。たとえば、Java のサーブレット仕様では、HTTP リクエストの処理プロセス全体を含むdoGet()
メソッド、具体的な実装はサーブレットのサブクラスによって完了します。
テンプレート メソッド パターンを使用する別の例は、GUI ライブラリのウィンドウ クラスです。ウィンドウ クラスは、開く、閉じる、レンダリングなどの操作のフレームワークを定義し、特定の実装はサブクラスによって行われます。
つまり、テンプレート メソッド パターンは、複雑なアルゴリズムの実装に役立ち、実装プロセス中のアルゴリズムの一貫性とスケーラビリティを確保できます。
3. 観察者パターン
オブザーバー パターン (Observer Pattern) は、1 対多の依存関係を定義する動作設計パターンであり、監視対象オブジェクトの状態が変化すると、その依存関係すべてに通知され、自動的に更新されます。
オブザーバー パターンは次の役割で構成されます。
- 件名: 監視対象オブジェクトは、オブザーバーの追加、削除、通知のためのインターフェイスを定義します。
- オブザーバー: オブザーバーは、通知を受信し、ステータスを更新するためのインターフェイスを定義します。
- ConcreteSubject: 特定の監視対象オブジェクトは、独自のオブザーバー リストを維持し、状態の変化をオブザーバーに通知する責任があります。
- ConcreteObserver: 特定のオブザーバーは、特定の監視対象オブジェクトへの参照を維持し、独自の更新メソッドを実装します。
単純なオブザーバー パターンのサンプル コードを次に示します。
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String message);
}
interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers(String message);
}
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " received message: " + message);
}
}
public class ObserverDemo {
public static void main(String[] args) {
Subject subject = new ConcreteSubject();
Observer observer1 = new ConcreteObserver("Observer1");
Observer observer2 = new ConcreteObserver("Observer2");
subject.attach(observer1);
subject.attach(observer2);
subject.notifyObservers("Hello World!");
subject.detach(observer2);
subject.notifyObservers("Goodbye World!");
}
}
この例では、ConcreteSubject がオブザーバーとして機能し、ConcreteObserver がオブザーバーとして機能します。ConcreteSubject の状態が変化すると、すべてのオブザーバーに通知し、その update メソッドを呼び出します。
Java では、オブザーバー パターンにも多くのアプリケーション シナリオがあります。以下にいくつかの例を示します。
1. Java 独自のイベント監視メカニズム
Java AWT と Swing コンポーネント ライブラリは両方とも、オブザーバー パターンを使用してイベント リスナー メカニズムを実装します。イベントが発生すると、Javaはイベントソースに登録されているリスナーのメソッドを呼び出します。
2. Springフレームワークのイベントリスナー機構
Spring フレームワークでは、ApplicationEvent インターフェイスと ApplicationListener インターフェイスを使用して、柔軟なイベント監視メカニズムが実装されています。アプリケーションでイベントが発生すると、Spring コンテナは登録されているすべてのリスナーに通知します。
3.グアバイベントバス
イベント バス フレームワークは Google Guava ライブラリで提供されており、開発者はイベント駆動型プログラミングをより簡単に実装できます。イベントバスフレームワークはオブザーバーパターンをベースとし、リフレクションやダイナミックプロキシなどの技術を利用し、効率的なイベント配信機構を実現します。
3.戦略モード
戦略パターンは、一連のアルゴリズムを定義し、相互に置き換えられるように各アルゴリズムをカプセル化する動作設計パターンです。これにより、アルゴリズムを使用するクライアントとは独立してアルゴリズムを変更できます。
以下は、Strategy パターンのサンプル コードです。
// 策略接口
public interface SortingStrategy {
public void sort(int[] arr);
}
// 快速排序策略
public class QuickSortStrategy implements SortingStrategy {
@Override
public void sort(int[] arr) {
// 实现快速排序算法
}
}
// 归并排序策略
public class MergeSortStrategy implements SortingStrategy {
@Override
public void sort(int[] arr) {
// 实现归并排序算法
}
}
// 策略上下文
public class Sorter {
private SortingStrategy strategy;
public void setStrategy(SortingStrategy strategy) {
this.strategy = strategy;
}
public void sort(int[] arr) {
strategy.sort(arr);
}
}
// 客户端代码
public static void main(String[] args) {
int[] arr = {5, 2, 8, 6, 1, 9};
Sorter sorter = new Sorter();
sorter.setStrategy(new QuickSortStrategy());
sorter.sort(arr); // 快速排序
sorter.setStrategy(new MergeSortStrategy());
sorter.sort(arr); // 归并排序
}
SortingStrategy
上記のサンプル コードでは、インターフェイスと 2 つの具象アルゴリズム クラス を定義しQuickSortStrategy
、MergeSortStrategy
両方ともSortingStrategy
インターフェイスを実装します。次に、さまざまなアルゴリズム実装を設定できるインターフェイス型のメンバー変数Sorter
を保持するクラスを定義しますSortingStrategy
。最後に、クライアント コードでは、Sorter
オブジェクトのsetStrategy()
メソッドを呼び出してさまざまなアルゴリズムを設定し、sort()
メソッドを呼び出して並べ替えることができます。
Strategy パターンの利点は、アルゴリズムを使用するクライアントとは独立してアルゴリズムを変更できることです。クライアント コードは、それ自体の実装を変更せずに、別のアルゴリズムを使用できます。さらに、戦略パターンでは多数の条件ステートメントの使用を回避することもできるため、コードがより簡潔で読みやすくなります。
Strategy パターンの実際の応用例としては、オンライン ショッピング サイトのチェックアウト システムがあります。決済システムは、ユーザーが選択した支払い方法に従って商品の価格を計算する必要があります。異なる支払い方法には異なる優先ポリシーがある場合があるため、異なる支払い方法を異なる戦略クラスとして実装し、決済システムの戦略モードを使用して計算に適切な支払い戦略を選択できます。これにより、請求システムのコードを変更することなく、支払い方法を簡単に追加または変更できるようになります。
4. 責任連鎖モデル
Chain of Responsibility パターンは、ハンドラーがリクエストを処理するまで、ハンドラーのチェーンにリクエストを送信できるようにする動作設計パターンです。各ハンドラーは次のハンドラーへの参照を持っているため、チェーンが形成されます。
Chain of Responsibility パターンでは、クライアントがチェーン内の最初のプロセッサにリクエストを送信し、チェーン内の各プロセッサがそのリクエストを処理する機会を取得します。プロセッサがリクエストを処理できる場合は、リクエストを処理して結果を返します。そうでない場合は、リクエストが処理されるまでチェーン内の次のプロセッサにリクエストを渡します。
責任連鎖パターンの簡単な例としては、Web サイトのチート対策システムが挙げられます。ユーザーがサイト上でコメントの送信や投稿の投稿などの特定のアクティビティを実行すると、ユーザーがロボットではないことを確認するためにシステムによって検証されます。通常、システムは複数の段階で構成されており、それぞれが異なる検証メカニズムを備えています。1 つのステージで検証に失敗した場合、リクエストが検証されるか、すべてのステージで検証が失敗するまで、リクエストは次のステージに渡されて処理されます。
簡単な Java 実装例を次に示します。
public abstract class Handler {
private Handler nextHandler;
public Handler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
public void handleRequest(Request request) {
if (canHandleRequest(request)) {
handle(request);
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
} else {
System.out.println("No handler found for the request.");
}
}
protected abstract boolean canHandleRequest(Request request);
protected abstract void handle(Request request);
}
public class Request {
private String message;
public Request(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
public class AuthenticationHandler extends Handler {
public AuthenticationHandler(Handler nextHandler) {
super(nextHandler);
}
@Override
protected boolean canHandleRequest(Request request) {
return request.getMessage().startsWith("authenticate:");
}
@Override
protected void handle(Request request) {
System.out.println("Authenticating the request: " + request.getMessage());
}
}
public class AuthorizationHandler extends Handler {
public AuthorizationHandler(Handler nextHandler) {
super(nextHandler);
}
@Override
protected boolean canHandleRequest(Request request) {
return request.getMessage().startsWith("authorize:");
}
@Override
protected void handle(Request request) {
System.out.println("Authorizing the request: " + request.getMessage());
}
}
public class Main {
public static void main(String[] args) {
Handler handlerChain = new AuthenticationHandler(new AuthorizationHandler(null));
Request request1 = new Request("authenticate:user1");
handlerChain.handleRequest(request1);
Request request2 = new Request("authorize:user2");
handlerChain.handleRequest(request2);
Request request3 = new Request("invalid_request");
handlerChain.handleRequest(request3);
}
}
責任連鎖パターンは、多くのオープンソース フレームワークで広く使用されています。いくつかの例を次に示します。
-
Java Servlet API のフィルター モード。各フィルターはリクエストをインターセプトして処理できます。リクエストは最初に最初のフィルターによって処理され、リクエストが処理されるまで順番に次のフィルターに渡されます。
-
Spring Framework の AOP (アスペクト指向プログラミング) は、Chain of Responsibility パターンに基づいて実装されます。Spring AOP を使用すると、横断的な関心事 (トランザクション管理、ロギングなど) をアプリケーションのビジネス ロジックから分離し、一連のエンハンサーを通じてターゲット オブジェクトのメソッド呼び出しにそれらを織り込むことができます。
-
Apache Tomcat のバルブ モード。Tomcat はオープンソースの Web コンテナであり、Valve と呼ばれるパターンを使用して、HTTP リクエストのフィルタリング、アクセス制御などの多くの機能を実装します。各 Valve はリクエストを処理し、リクエストが処理されるまで次の Valve にリクエストを渡すことができます。
-
Netty の ChannelPipeline パターン。Netty は、NIO をベースとした高性能ネットワーク アプリケーション フレームワークであり、責任連鎖モデルを採用してデータの処理と送信を実現します。Netty の ChannelPipeline には一連の ChannelHandler が含まれており、各 Handler はデータを処理し、そのデータを次の Handler に渡すことができます。
つまり、責任連鎖パターンは、リクエストの処理を複数のステップに分割し、処理フローを動的に変更するのに役立つ非常に一般的な設計パターンです。これにより、システムの柔軟性と拡張性が向上し、システムの保守性と再利用性が向上します。
5. 通訳モード
インタプリタ パターンは、特定の問題を解決するために使用される動作設計パターンです。このパターンの中心的な考え方は、言語文法を定義し、その言語のステートメントを解釈するインタープリターを定義することです。インタプリタ モードは主に次の 2 つの部分で構成されます。
- 抽象式 (Abstract Expression): インタプリタのインターフェイスを定義します。通常、interprete() メソッドが 1 つだけ含まれます。
- 具体的な式: 抽象的な式インターフェイスを実装し、その式を具体的に説明します。
以下にインタープリター モードの簡単な例を示します。
// 抽象表达式
interface Expression {
boolean interpret(String context);
}
// 具体表达式
class TerminalExpression implements Expression {
private String data;
public TerminalExpression(String data) {
this.data = data;
}
@Override
public boolean interpret(String context) {
if (context.contains(data)) {
return true;
}
return false;
}
}
// 或表达式
class OrExpression implements Expression {
private Expression expression1;
private Expression expression2;
public OrExpression(Expression expression1, Expression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}
@Override
public boolean interpret(String context) {
return expression1.interpret(context) || expression2.interpret(context);
}
}
// 与表达式
class AndExpression implements Expression {
private Expression expression1;
private Expression expression2;
public AndExpression(Expression expression1, Expression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}
@Override
public boolean interpret(String context) {
return expression1.interpret(context) && expression2.interpret(context);
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
Expression orExpression = new OrExpression(robert, john);
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
Expression andExpression = new AndExpression(julie, married);
System.out.println(orExpression.interpret("John"));
System.out.println(andExpression.interpret("Julie Married"));
}
}
Java ではインタプリタ モードが使用されることは比較的まれですが、データベース クエリ言語での解析や正規表現解析など、一部のドメイン固有言語の解析には依然として適用されています。
正規表現を例に挙げると、java.util.regex
Java のパッケージはインタープリター モードに基づいて実装されます。このパッケージでは、Pattern
クラスは正規表現パターンを表し、Matcher
クラスはパターンの照合結果を表します。照合する場合、Pattern
正規表現はインタプリタ オブジェクトにコンパイルされ、Matcher
入力文字列がクラスを通じて解析および照合されます。
具体的には、Pattern
このクラスは、インタープリター モードの結合モードを使用して、複数のインタープリター オブジェクトを結合し、正規表現全体のインタープリター オブジェクト ツリーを構築します。このツリー構造には複数のリーフ ノードが含まれており、各リーフ ノードは文字や文字クラスなどの基本的な正規表現構文要素を表し、非リーフ ノードは 1 つ以上の文字などの複数の基本要素の組み合わせを表します。複数の文字クラスの結合、または複数の文字クラスの結合。Matcher
このクラスはイテレータ パターンを使用してインタプリタ オブジェクト ツリーを走査し、入力文字列を解析して照合します。
つまり、Java でのインタープリター モードのアプリケーションは比較的特殊であり、通常はドメイン固有言語の解析や正規表現などの特定のシナリオで使用されます。