デザイン パターンの美しさ 56-オブザーバー モード (パート 1): さまざまなアプリケーション シナリオにおけるオブザーバー モードのさまざまな実装の詳細な説明

56 | オブザーバー モード (パート 1): さまざまなアプリケーション シナリオにおけるオブザーバー モードのさまざまな実装の詳細な説明

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

より多くの行動設計パターンがあり、そのうちの 11 が 23 の古典的な設計パターンのほぼ半分を占めています。オブザーバー モード、テンプレート モード、ストラテジー モード、チェーン オブ 責任モード、ステート モード、イテレーター モード、ビジター モード、メモ モード、コマンド モード、インタープリター モード、中間モードです。

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

早速、今日から本格的に勉強を始めましょう!

原理と応用シナリオ分析

Observerデザイン パターンは、Publish-Subscribe デザイン パターンも呼ばれます。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 フィード、本質的にこのモードの影を持っています。観察者パターン。さまざまなアプリケーション シナリオと要件の下で、このモードには、同期ブロッキング実装と非同期非ブロッキング実装を含む完全に異なる実装方法もあり、プロセス内実装とプロセス間実装があります。

クラスディスカッション

  1. 「生産者・消費者」モデルとオブザーバーモデルの違いとつながりを比べてみてください。
  2. 今日言及されたオブザーバー モードのいくつかのアプリケーション シナリオ (電子メールのサブスクリプションなど) に加えて、他のアプリケーション シナリオを考えられますか?

メッセージを残して、あなたの考えを私と共有してください。何かを得た場合は、この記事を友達と共有してください。

おすすめ

転載: blog.csdn.net/fegus/article/details/130498818