オタクタイム-デザインパターンの美しさブリッジモード:さまざまなタイプとチャネルをサポートするメッセージプッシュシステムを実装する方法は?

ブリッジモードのコード実装は非常に単純ですが、理解するのが少し難しく、アプリケーションシナリオも比較的制限されているため、プロキシモードと同様に、ブリッジモードは実際のプロジェクトではあまり一般的に使用されていません。簡単に理解する必要があるだけです。を参照してください。私たちがそれを知ることができる限り、それは私たちの研究の焦点では​​ありません。

ブリッジモードの原理分析

ブリッジモード、別名ブリッジモード、英語はブリッジデザインパターンです。このパターンは、23のデザインパターンの中で最も理解しにくいものの1つと言えます。さらに多くの本や資料を調べたところ、このモデルを理解する方法は2つあることがわかりました。

もちろん、それらの間で理解する「最も純粋な」方法は、GoFの「DesignPatterns」ブックのブリッジモードの定義です。結局のところ、これらの23の古典的なデザインパターンは、もともとこの本によって要約されました。GoFの「DesignPatterns」の本では、ブリッジパターンは次のように定義されています。「抽象化を実装から切り離して、2つを独立して変化させることができます。」中国語に翻訳すると、「抽象化を実装から切り離して、2つを独立して変化させることができます。」独立した変更。」

ブリッジングモデルに関しては、多くの本や資料に別の理解方法があります。「クラスには2つ(またはそれ以上)の独立して変化する次元があります。これら2つ(またはそれ以上)の次元を組み合わせます。独立して拡張することができます。」継承階層の指数関数的な爆発を回避するために、継承関係は構成関係に置き換えられます。この理解の仕方は、先ほどお話しした「継承よりも構成の方が良い」という設計原理とよく似ているので、ここでは説明しません。GoFの理解に焦点を当てましょう。

oFの定義は非常に簡潔で、この文だけでは、それが何を意味するのか理解できる人はほとんどいないと推定されます。それでは、JDBCドライバーの例を通して説明しましょう。JDBCドライバーは、ブリッジモードの古典的なアプリケーションです。まず、JDBCドライバーを使用してデータベースを照会する方法を見てみましょう。具体的なコードは次のとおりです。


Class.forName("com.mysql.jdbc.Driver");//加载及注册JDBC驱动程序
String url = "jdbc:mysql://localhost:3306/sample_db?user=root&password=your_password";
Connection con = DriverManager.getConnection(url);
Statement stmt = con.createStatement();
String query = "select * from test";
ResultSet rs=stmt.executeQuery(query);
while(rs.next()) {
    
    
  rs.getString(1);
  rs.getInt(2);
}

MySQLデータベースをOracleデータベースに変更する場合は、コードの最初の行のcom.mysql.jdbc.Driverをoracle.jdbc.driver.OracleDriverに変更するだけで済みます。もちろん、より柔軟な実装もあります。構成ファイルにロードする必要のあるDriverクラスを記述し、プログラムの起動時に構成ファイルから自動的にロードできるため、データベースを切り替えるときにコードを変更する必要はありません。構成ファイルを変更するだけです。

コードを変更するのか構成を変更するのかに関係なく、プロジェクトでは、あるデータベースから別のデータベースに切り替えるには、コードを少し変更するだけでよいか、コードをまったく変更しません。

ソースコードの下に秘密はありません。この問題を明確にするために、最初にcom.mysql.jdbc.Driverクラスのコードを調べます。関連するコードの一部を抜粋しました。ここに置いてください。ご覧ください。


package com.mysql.jdbc;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    
    
  static {
    
    
    try {
    
    
      java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
    
    
      throw new RuntimeException("Can't register driver!");
    }
  }

  /**
   * Construct a new driver and register it with DriverManager
   * @throws SQLException if a database error occurs.
   */
  public Driver() throws SQLException {
    
    
    // Required for Class.forName().newInstance()
  }
}

com.mysql.jdbc.Driverのコード実装と組み合わせると、ステートメントClass.forName( "com.mysql.jdbc.Driver")が実行されると、実際には2つのことが実行されることがわかります。最初に、指定されたDriverクラスを見つけてロードするようにJVMに依頼します。次に、このクラスの静的コードを実行します。これは、MySQLドライバーをDriverManagerクラスに登録することです。

それでは、DriverManagerクラスの目的をもう一度見てみましょう。具体的なコードを以下に示します。特定のDriver実装クラス(com.mysql.jdbc.Driverなど)をDriverManagerに登録すると、JDBCインターフェイスへの後続のすべての呼び出しは、実行のために特定のDriver実装クラスに委任されます。Driver実装クラスはすべて同じインターフェース(java.sql.Driver)を実装しているため、Driverを柔軟に切り替えることができます。


public class DriverManager {
    
    
  private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();

  //...
  static {
    
    
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
  }
  //...

  public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {
    
    
    if (driver != null) {
    
    
      registeredDrivers.addIfAbsent(new DriverInfo(driver));
    } else {
    
    
      throw new NullPointerException();
    }
  }

  public static Connection getConnection(String url, String user, String password) throws SQLException {
    
    
    java.util.Properties info = new java.util.Properties();
    if (user != null) {
    
    
      info.put("user", user);
    }
    if (password != null) {
    
    
      info.put("password", password);
    }
    return (getConnection(url, info, Reflection.getCallerClass()));
  }
  //...
}

ブリッジモードの定義は、「抽象化と実装を分離して、それらが独立して変更できるようにする」ことです。定義における「抽象化」と「実装」の2つの概念を理解することは、ブリッジングモデルを理解するための鍵です。JDBCの場合、「抽象化」とは何ですか?「実現」とは?

実際、JDBC自体は「抽象化」に相当します。ここで言及されている「抽象化」は、「抽象クラス」または「インターフェース」ではなく、特定のデータベースとは関係のない抽象化された「クラスライブラリ」のセットを指すことに注意してください。特定のドライバー(たとえば、com.mysql.jdbc.Driver)は、「実装」と同等です。ここでいう「実装」とは、「インターフェースの実装クラス」ではなく、特定のデータベースに関連する「クラスライブラリ」のセットを意味することに注意してください。JDBCとドライバーは独立して開発され、オブジェクトの組み合わせによって一緒に組み立てられます。JDBCのすべての論理操作は、最終的に実行のためにドライバーに委託されます。

私はあなたが理解するのを助けるために絵を描きました、あなたは私の説明と一緒にそれを読むことができます。

ここに写真の説明を挿入

ブリッジモードの適用例

APIインターフェイス監視アラームの例について説明しました。さまざまなアラームルールに従って、さまざまなタイプのアラームがトリガーされます。アラームは、電子メール、SMS、WeChat、自動音声通話など、複数の通知チャネルをサポートしています。通知の緊急性には、SEVERE(重大)、URGENCY(緊急)、NORMAL(通常)、TRIVIAL(無関係)など、さまざまな種類があります。異なる緊急度レベルは、異なる通知チャネルに対応します。たとえば、SERVE(重大)レベルのメッセージは、「自動音声通話」を通じて関係者に通知されます。

当時のコード実装では、アラーム情報を送信するコード部分の大まかな設計のみを行っていたので、一緒に実装してみましょう。まず、最も単純で直接的な実装方法を見てみましょう。コードは次のとおりです。


public enum NotificationEmergencyLevel {
    
    
  SEVERE, URGENCY, NORMAL, TRIVIAL
}

public class Notification {
    
    
  private List<String> emailAddresses;
  private List<String> telephones;
  private List<String> wechatIds;

  public Notification() {
    
    }

  public void setEmailAddress(List<String> emailAddress) {
    
    
    this.emailAddresses = emailAddress;
  }

  public void setTelephones(List<String> telephones) {
    
    
    this.telephones = telephones;
  }

  public void setWechatIds(List<String> wechatIds) {
    
    
    this.wechatIds = wechatIds;
  }

  public void notify(NotificationEmergencyLevel level, String message) {
    
    
    if (level.equals(NotificationEmergencyLevel.SEVERE)) {
    
    
      //...自动语音电话
    } else if (level.equals(NotificationEmergencyLevel.URGENCY)) {
    
    
      //...发微信
    } else if (level.equals(NotificationEmergencyLevel.NORMAL)) {
    
    
      //...发邮件
    } else if (level.equals(NotificationEmergencyLevel.TRIVIAL)) {
    
    
      //...发邮件
    }
  }
}

//在API监控告警的例子中,我们如下方式来使用Notification类:
public class ErrorAlertHandler extends AlertHandler {
    
    
  public ErrorAlertHandler(AlertRule rule, Notification notification){
    
    
    super(rule, notification);
  }


  @Override
  public void check(ApiStatInfo apiStatInfo) {
    
    
    if (apiStatInfo.getErrorCount() > rule.getMatchedRule(apiStatInfo.getApi()).getMaxErrorCount()) {
    
    
      notification.notify(NotificationEmergencyLevel.SEVERE, "...");
    }
  }
}

Notificationクラスのコード実装に関する最も明らかな問題は、if-else分岐ロジックが多数あることです。実際、各ブランチのコードが複雑でなく、後の期間に無制限に拡張される可能性がない場合(if-elseブランチの判断を追加)、そのような設計上の問題は大きくなく、if-elseを放棄する必要はありません。分岐ロジック。

ただし、通知コードは明らかにこの条件を満たしていません。各if-elseブランチのコードロジックはより複雑であるため、通知を送信するためのすべてのロジックは、Notificationクラスに積み上げられます。クラスのコードが多いほど、読みにくくなり、変更しにくいほど、メンテナンスコストが高くなることがわかっています。多くの設計パターンは、巨大なクラスをより小さなクラスに分割し、より合理的な構造でそれらを組み立てようとします。

通知コードでは、さまざまなチャネルの送信ロジックを取り除いて、独立したメッセージ送信クラス(MsgSender関連クラス)を形成しました。その中で、Notificationクラスは抽象化に相当し、MsgSenderクラスは実装に相当します。この2つは独立して開発でき、組み合わせ関係(つまり、ブリッジ)を介して任意に組み合わせることができます。いわゆる任意の組み合わせとは、緊急度の異なるメッセージと送信チャネルの対応がコードで固定されていないことを意味しますが、動的に指定できます(たとえば、構成を読み取って対応を取得します)。

この設計アイデアに従って、コードをリファクタリングしました。リファクタリング後のコードは次のとおりです。


public interface MsgSender {
    
    
  void send(String message);
}

public class TelephoneMsgSender implements MsgSender {
    
    
  private List<String> telephones;

  public TelephoneMsgSender(List<String> telephones) {
    
    
    this.telephones = telephones;
  }

  @Override
  public void send(String message) {
    
    
    //...
  }

}

public class EmailMsgSender implements MsgSender {
    
    
  // 与TelephoneMsgSender代码结构类似,所以省略...
}

public class WechatMsgSender implements MsgSender {
    
    
  // 与TelephoneMsgSender代码结构类似,所以省略...
}

public abstract class Notification {
    
    
  protected MsgSender msgSender;

  public Notification(MsgSender msgSender) {
    
    
    this.msgSender = msgSender;
  }

  public abstract void notify(String message);
}

public class SevereNotification extends Notification {
    
    
  public SevereNotification(MsgSender msgSender) {
    
    
    super(msgSender);
  }

  @Override
  public void notify(String message) {
    
    
    msgSender.send(message);
  }
}

public class UrgencyNotification extends Notification {
    
    
  // 与SevereNotification代码结构类似,所以省略...
}
public class NormalNotification extends Notification {
    
    
  // 与SevereNotification代码结构类似,所以省略...
}
public class TrivialNotification extends Notification {
    
    
  // 与SevereNotification代码结构类似,所以省略...
}

おすすめ

転載: blog.csdn.net/zhujiangtaotaise/article/details/110471852