[デザイン パターンとパラダイム: 動作] 56 | オブザーバー モード (パート 1): さまざまなアプリケーション シナリオにおけるオブザーバー モードのさまざまな実装の詳細な説明

私たちは多くの場合、23 の古典的なデザイン パターンを創造、構造、行動の 3 つのカテゴリに分類します。これまで作成と構造について学びましたが、今日からは動作デザインパターンについて学び始めます。創造的デザインパターンは主に「オブジェクトの作成」の問題を解決し、構造的デザインパターンは主に「クラスまたはオブジェクトの組み合わせまたはアセンブリ」の問題を解決し、動作的デザインパターンは主に「オブジェクト間の相互作用」の問題を解決することがわかっています。クラスまたはオブジェクト"."質問。

動作デザイン パターンは 11 あり、23 の古典的なデザイン パターンのほぼ半分を占めます。オブザーバー モード、テンプレート モード、戦略モード、責任連鎖モード、状態モード、イテレーター モード、ビジター モード、メモ モード、コマンド モード、インタープリター モード、仲介モードです。

私たちは多くの場合、23 の古典的なデザイン パターンを創造、構造、行動の 3 つのカテゴリに分類します。これまで作成と構造について学びましたが、今日からは動作デザインパターンについて学び始めます。創造的デザインパターンは主に「オブジェクトの作成」の問題を解決し、構造的デザインパターンは主に「クラスまたはオブジェクトの組み合わせまたはアセンブリ」の問題を解決し、動作的デザインパターンは主に「オブジェクト間の相互作用」の問題を解決することがわかっています。クラスまたはオブジェクト"."質問。

動作デザイン パターンは 11 あり、23 の古典的なデザイン パターンのほぼ半分を占めます。オブザーバー モード、テンプレート モード、戦略モード、責任連鎖モード、状態モード、イテレーター モード、ビジター モード、メモ モード、コマンド モード、インタープリター モード、仲介モードです。

今日は、最初の動作設計パターンを学習します。これは、実際の開発でよく使用されるパターンでもあります。オブザーバー パターンです。さまざまなアプリケーション シナリオに従って、オブザーバー モードはさまざまなコード実装メソッドに対応します: 同期ブロッキング実装メソッドと非同期ノンブロッキング実装メソッドもあり、プロセス内実装メソッドとクロスプロセス実装メソッドがあります。今日は、原理、実装、および応用シナリオの説明に重点を置きます。次のクラスでは、オブザーバー パターンに基づいた非同期でノンブロッキングの EventBus を実装して、このパターンの理解を深めます。

それでは早速、今日の学習を正式に始めましょう!

原理と応用シナリオの分析

オブザーバー デザイン パターンは、パブリッシュ/サブスクライブ デザイン パターンとも呼ばれます。GoF の「デザイン パターン」の本では、次のように定義されています。

オブジェクト間の 1 対多の依存関係を定義して、1 つのオブジェクトの状態が変化すると、そのすべての依存オブジェクトに通知が送信され、自動的に更新されます。

中国語に翻訳すると、オブジェクト間に 1 対多の依存関係を定義します。オブジェクトの状態が変化すると、すべての依存オブジェクトに自動的に通知されます。

一般に、依存するオブジェクトをオブザーバブル(Observable)と呼び、依存するオブジェクトをオブザーバ(Observer)と呼ぶ。ただし、実際のプロジェクト開発では、これら 2 つのオブジェクトの名前は比較的柔軟であり、Subject-Observer、Publisher-Subscriber、Producer-Consumer、EventEmitter-EventListener、Dispatcher-Listener など、さまざまな名前が存在します。どのように呼んでも、アプリケーション シナリオが上記の定義に準拠している限り、オブザーバー モードとみなすことができます。

実際、オブザーバー モードは比較的抽象的なモードであり、さまざまなアプリケーション シナリオや要件に応じて、まったく異なる実装方法が存在しますが、これについては後ほど詳しく説明します。次に、最も古典的な実装の 1 つを見てみましょう。これは、このモードについて説明するときに多くの書籍や資料で示されている最も一般的な実装方法でもあります。具体的なコードは次のとおりです。

public interface Subject {
  void registerObserver(Observer observer);
  void removeObserver(Observer observer);
  void notifyObservers(Message message);
}
public interface Observer {
  void update(Message message);
}
public class ConcreteSubject implements Subject {
  private List<Observer> observers = new ArrayList<Observer>();
  @Override
  public void registerObserver(Observer observer) {
    observers.add(observer);
  }
  @Override
  public void removeObserver(Observer observer) {
    observers.remove(observer);
  }
  @Override
  public void notifyObservers(Message message) {
    for (Observer observer : observers) {
      observer.update(message);
    }
  }
}
public class ConcreteObserverOne implements Observer {
  @Override
  public void update(Message message) {
    //TODO: 获取消息通知,执行自己的逻辑...
    System.out.println("ConcreteObserverOne is notified.");
  }
}
public class ConcreteObserverTwo implements Observer {
  @Override
  public void update(Message message) {
    //TODO: 获取消息通知,执行自己的逻辑...
    System.out.println("ConcreteObserverTwo is notified.");
  }
}
public class Demo {
  public static void main(String[] args) {
    ConcreteSubject subject = new ConcreteSubject();
    subject.registerObserver(new ConcreteObserverOne());
    subject.registerObserver(new ConcreteObserverTwo());
    subject.notifyObservers(new Message());
  }
}

実際、上記のコードはオブザーバー モードの「テンプレート コード」とみなされ、一般的な設計アイデアのみを反映できます。実際のソフトウェア開発では、上記のテンプレート コードをコピーする必要はありません。オブザーバー モードの実装にはさまざまな方法があり、登録関数はアタッチ、削除関数はデタッチなど、ビジネス シナリオに応じて関数やクラスの名前が大きく異なります。ただし、すべてが同じままであり、設計思想もほぼ同じです。

原理とコードの実装は非常にシンプルで理解しやすいので、多くの説明は必要ありません。特定の例に焦点を当ててみましょう。どのような状況でこのデザイン パターンを使用する必要があるのでしょうか? 言い換えれば、このデザインパターンはどのような問題を解決できるのでしょうか?

P2P投資・財務管理システムを開発しているとすると、ユーザー登録が成功すると、ユーザーに投資体験ファンドを発行します。コード実装は大まかに次のようになります。

public class UserController {
  private UserService userService; // 依赖注入
  private PromotionService promotionService; // 依赖注入
  public Long register(String telephone, String password) {
    //省略输入参数的校验代码
    //省略userService.register()异常的try-catch代码
    long userId = userService.register(telephone, password);
    promotionService.issueNewUserExperienceCash(userId);
    return userId;
  }
}

登録インターフェイスは登録と経験値の発行という 2 つのことを行いますが、これは単一責任の原則に違反しますが、拡張や変更の必要がない場合は、現在のコードの実装が許容されます。オブザーバー モードを使用する必要がある場合は、より多くのクラスとより複雑なコード構造を導入する必要がありますが、これは過剰設計です。

逆に、登録成功後など需要が頻繁に変化する場合は、体験料は発行されなくなり、代わりにクーポンが発行され、サイト内に「登録成功へようこそ」の文字が表示されます。ユーザーに送信されます。この場合、 register() 関数のコードを頻繁に変更する必要がありますが、これはオープンとクローズの原則に違反します。さらに、登録が成功した後にさらに多くのフォローアップ操作を実行する必要がある場合、 register() 関数のロジックはますます複雑になり、コードの可読性と保守性に影響を与えます。

このとき、オブザーバーモードが便利です。Observer パターンを使用して、上記のコードをリファクタリングしました。リファクタリング後のコードは次のようになります。

public interface RegObserver {
  void handleRegSuccess(long userId);
}
public class RegPromotionObserver implements RegObserver {
  private PromotionService promotionService; // 依赖注入
  @Override
  public void handleRegSuccess(long userId) {
    promotionService.issueNewUserExperienceCash(userId);
  }
}
public class RegNotificationObserver implements RegObserver {
  private NotificationService notificationService;
  @Override
  public void handleRegSuccess(long userId) {
    notificationService.sendInboxMessage(userId, "Welcome...");
  }
}
public class UserController {
  private UserService userService; // 依赖注入
  private List<RegObserver> regObservers = new ArrayList<>();
  // 一次性设置好,之后也不可能动态的修改
  public void setRegObservers(List<RegObserver> observers) {
    regObservers.addAll(observers);
  }
  public Long register(String telephone, String password) {
    //省略输入参数的校验代码
    //省略userService.register()异常的try-catch代码
    long userId = userService.register(telephone, password);
    for (RegObserver observer : regObservers) {
      observer.handleRegSuccess(userId);
    }
    return userId;
  }
}

たとえば、ユーザーが正常に登録した後、新しいオブザーバーを追加する必要がある場合は、オブザーバー モードのコード実装である UserController クラスの register() 関数に基づいて、ユーザー登録情報をビッグデータ信用情報システムにプッシュします。まったく変更する必要はありません。RegObserver インターフェイスを実装する別のクラスを追加し、setRegObservers() 関数を通じてそれを UserController クラスに登録するだけです。

ただし、経験金の送信をクーポンの送信に置き換える場合、RegPromotionObserver クラスの handleRegSuccess() 関数のコードを変更する必要があると言われるかもしれませんが、これはやはりオープンクローズ原則に違反しますか? そのとおりですが、 register() 関数と比較すると、handleRegSuccess() 関数のロジックははるかに単純で、変更によるエラーが発生しにくく、バグが発生するリスクも低くなります。

私たちはこれまでに多くのデザイン パターンを学習してきましたが、実際にデザイン パターンが行うことは分離であることを発見したかどうかはわかりません。作成モードは作成コードと使用コードを分離すること、構造モードはさまざまな機能コードを分離すること、動作モードはさまざまな動作コードを分離することです。オブザーバー モードに特有の、これはオブザーバーと観察されるコードの分離です。デザインパターンの助けを借りて、より良いコード構造を使用して、大量のコードをより多くの単一の責任を持つ小さなクラスに分割し、開始と終了の原則、高い凝集性と疎結合などの特性を満たすようにします。コードの問題の制御と対処に関しては、複雑さ、コードのスケーラビリティの向上です。

さまざまなアプリケーションシナリオに基づくさまざまな実装方法

オブザーバー モードには、コード レベルでのデカップリングからアーキテクチャ レベルでのシステム デカップリング、または一部の製品設計アイデアに至るまで、幅広いアプリケーション シナリオがあり、そのすべてに電子メール サブスクリプション、RSS フィード、基本的にこのモードの影が含まれています。観察者のパターン。

アプリケーションのシナリオや要件が異なると、このモードの実装方法も完全に異なります。冒頭でも説明しましたが、同期ブロッキング実装方法と非同期ノンブロッキング実装方法があり、プロセス内実装方法とプロセス間実装方法があります。 . 実現方法。
先ほどの実装方法は、先ほどの分類方法の観点から言えば、同期ブロッキングの実装方法です。オブザーバーと監視対象のコードは同じスレッドで実行され、すべてのオブザーバーのコードが実行されるまで監視対象はブロックされてから、後続のコードが実行されます。前述のユーザー登録の例と比較すると、 register() 関数は各オブザーバーの handleRegSuccess() 関数を順番に呼び出して実行し、実行が完了するまで結果はクライアントに返されません。

登録インターフェイスが頻繁に呼び出され、パフォーマンスに非常に敏感なインターフェイスであり、インターフェイスの応答時間ができるだけ短いことが望ましい場合は、同期ブロッキング実装を非同期非ブロッキング実装に変更できます。応答時間を短縮します。具体的には、userService.register() 関数が実行されると、新しいスレッドを開始してオブザーバーの handleRegSuccess() 関数を実行します。そのため、userController.register() 関数は、すべての handleRegSuccess() 関数が完了するまで待つ必要がありません。が実行され、完了後、結果がクライアントに返されます。userController.register() 関数は 3 つの SQL ステートメントを実行して返されるだけで、1 つの SQL ステートメントを実行して返されるだけであり、応答時間は元の約 1/3 に短縮されます。

では、非同期ノンブロッキングオブザーバーモードを実装するにはどうすればよいでしょうか? より簡単な方法は、各 handleRegSuccess() 関数のコードを実行する新しいスレッドを作成することです。ただし、EventBus に基づいた、より洗練された実装方法があります。今日はこれ以上の説明は省略します。次の講義では、1 回の授業時間を使って Google Guava EventBus フレームワークの設計アイデアを学び、非同期およびノンブロッキングをサポートする EventBus フレームワークの開発をガイドします。これは、非同期ノンブロッキング オブザーバー モードを必要とするあらゆるアプリケーション シナリオで再利用できます。

先ほど述べた 2 つのシナリオは、同期ブロッキング実装であっても、非同期非ブロッキング実装であっても、すべてインプロセス実装です。ユーザー登録が成功した場合、ユーザー情報をビッグデータ信用情報システムに送信する必要があります。ビッグデータ信用情報システムは独立したシステムであり、ビッグデータ信用情報システムとのやり取りは異なるプロセスにまたがるため、相互接続をどのように達成するかが課題となります。プロセス観察かモードか?
ビッグデータ信用情報システムがユーザー登録情報を送信するための RPC インターフェイスを提供する場合、以前の実装アイデアを引き続き使用し、handleRegSuccess() 関数で RPC インターフェイスを呼び出してデータを送信できます。ただし、Message Queue (ActiveMQ など) に基づいた、より洗練された一般的に使用される実装方法もあります。

もちろん、この実装方法には新たなシステム(メッセージキュー)を導入する必要があり、保守コストが増加するというデメリットもあります。ただし、その利点も非常に明白です。元の実装では、オブザーバーを監視対象に登録する必要があり、監視対象はメッセージを送信するためにオブザーバーを順番にトラバースする必要があります。メッセージ キューの実装に基づいて、監視対象とオブザーバー間の分離がより徹底され、2 つの部分間の結合が小さくなります。観察されるものは観察者をまったく意識していませんし、同様に、観察者も観察されるものをまったく意識しません。監視対象者はメッセージ キューにメッセージを送信するだけであり、オブザーバーはメッセージ キューからメッセージを読み取って対応するロジックを実行するだけです。

重要なレビュー

さて、今日の内容はここまでです。何に重点を置く必要があるのか​​、一緒にまとめて見直してみましょう。
デザイン パターンが行うことは分離です。作成パターンは作成コードと使用コードを分離します。構造パターンはさまざまな機能コードを分離します。動作パターンはさまざまな動作コードを分離します。オブザーバー パターンと観察されるコードを分離するオブザーバー パターンに固有です。デザイン パターンの助けを借りて、より適切なコード構造を使用して、大量のコードをより多くの単一の責任を持つ小さなクラスに分割し、開始と終了、高い凝集性と低い結合性などの特性を満たすことができるようにします。コードの問題の制御と対処に関しては、複雑さ、コードのスケーラビリティの向上です。

オブザーバー モードには、コード レベルでのデカップリングからアーキテクチャ レベルでのシステム デカップリング、または一部の製品設計アイデアに至るまで、幅広いアプリケーション シナリオがあり、そのすべてに電子メール サブスクリプション、RSS フィード、基本的にこのモードの影が含まれています。観察者のパターン。さまざまなアプリケーション シナリオや要件に応じて、このモードには同期ブロッキング実装や非同期ノンブロッキング実装など、まったく異なる実装方法があり、プロセス内実装とプロセス間実装があります。

クラスディスカッション

  • 「生産者・消費者」モデルとオブザーバー・モデルの違いとつながりを比較してください。
  • 電子メールの購読など、今日述べたオブザーバー モードのいくつかのアプリケーション シナリオに加えて、他のアプリケーション シナリオを思いつきますか?

おすすめ

転載: blog.csdn.net/qq_32907491/article/details/131179399