さまざまなデザインパターンを実務に応用
質問 1: ファクトリ パターンとビルダー パターンの違いは何ですか? 仕事でビルダー パターンを使用したことがありますか?
1. ビルダーパターンとは何ですか
ビルダー パターン は、ジェネレーター パターンとも呼ばれ、創造的なデザイン パターンです。
- 定義: 複雑なオブジェクトの構築と表現を分離し、同じ構築プロセスで異なる表現を作成できるようにします。
ビルダーパターンで解決すべき問題
- ビルダー パターンを使用すると、パーツを組み立てプロセスから分離し、複雑なオブジェクトを段階的に作成できます。ユーザーは、内部構造の詳細を知らなくても、複雑なオブジェクトのタイプを指定するだけでオブジェクトを取得できます。
2. ビルダーモードの構成
ビルダー モードには次の 4 つの役割が含まれます。
- 抽象ビルダー クラス (Builder): このインターフェイスは、複合オブジェクトのどの部分を作成するかを指定します。特定のコンポーネント オブジェクトの作成は含まれません。
- Concrete Builder クラス (ConcreteBuilder): 複雑な製品の各コンポーネントの特定の作成メソッドを完了するための Builder インターフェイスを実装します。構築プロセスが完了したら、作成された責任のある製品オブジェクトを返すメソッドを提供します。
- 製品クラス (Product): 作成される複合オブジェクト (複数のコンポーネントを含む)。
- Director クラス (Director): 特定のビルダーを呼び出して、複雑なオブジェクトの各部分を作成します。ディレクターは、特定の製品情報には関与せず、オブジェクトの各部分が完全に、または特定の順序 (クライアント) で作成されることを保証することのみを担当します。通常は指揮官と対話するだけで済みます)。
3. 実際の開発におけるビルダーモードの適用
需要分析:
- フロントエンド ユーザー操作、クエリ条件の選択、グループ化、並べ替え、ページング、その他のパラメーター SQL —> Oracle、MySQL
- システムは、Oracle、MySQL、DB2 などのさまざまな製品のデータベースを操作する必要があります。
- 各データベースに特定の Builder クラスを記述し、コマンダーのスプライスで、パラメータに従って順序、場所、順序、グループ、およびページングを順番に選択させます。
- 文字列型のSQL文とパラメータリストを生成する
- 製品カテゴリ
// 产品类
public class SqlQuery {
private String select;
private String from;
private String where;
private String groupBy;
private String orderBy;
private String limit;
public SqlQuery(String select, String from) {
this.select = select;
this.from = from;
}
public String getSelect() {
return select;
}
public void setSelect(String select) {
this.select = select;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getWhere() {
return where;
}
public String getGroupBy() {
return groupBy;
}
public String getOrderBy() {
return orderBy;
}
public String getLimit() {
return limit;
}
public void setWhere(String where) {
this.where = where;
}
public void setGroupBy(String groupBy) {
this.groupBy = groupBy;
}
public void setOrderBy(String orderBy) {
this.orderBy = orderBy;
}
public void setLimit(String limit) {
this.limit = limit;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("SELECT ").append(select).append(" FROM ").append(from);
if (where != null && !where.isEmpty()) {
sb.append(" WHERE ").append(where);
}
if (groupBy != null && !groupBy.isEmpty()) {
sb.append(" GROUP BY ").append(groupBy);
}
if (orderBy != null && !orderBy.isEmpty()) {
sb.append(" ORDER BY ").append(orderBy);
}
if (limit != null && !limit.isEmpty()) {
sb.append(" LIMIT ").append(limit);
}
return sb.toString();
}
}
- 抽象ビルダー
/**
* 抽象建造者类
* @author zxl
* @date 2023/4/20
**/
public abstract class SqlQueryBuilder {
protected SqlQuery sqlQuery;
public void createSqlQuery(String select, String from) {
sqlQuery = new SqlQuery(select, from);
}
public SqlQuery getSqlQuery() {
return sqlQuery;
}
//抽取共性步骤
public abstract void buildWhere();
public abstract void buildGroupBy();
public abstract void buildOrderBy();
public abstract void buildLimit();
}
- 具体建造者
/**
* 具体建造者类:用于创建MySQL数据库的SQL查询语句
* @author zxl
* @date 2023/4/20
**/
public class MySqlQueryBuilder extends SqlQueryBuilder {
@Override
public void buildWhere() {
sqlQuery.setWhere("1 = 1"); // MySQL不需要限制行数
}
@Override
public void buildGroupBy() {
sqlQuery.setGroupBy("deptno, ename, hiredate");
}
@Override
public void buildOrderBy() {
sqlQuery.setOrderBy("hiredate DESC");
}
@Override
public void buildLimit() {
sqlQuery.setLimit("0, 10"); // MySQL分页从0开始
}
}
/**
* 用于创建Oracle数据库的SQL查询语句
* @author zxl
* @date 2023/4/20
**/
public class OracleQueryBuilder extends SqlQueryBuilder {
@Override
public void buildWhere() {
sqlQuery.setWhere("rownum <= 1000"); // Oracle查询最多返回1000行数据
}
@Override
public void buildGroupBy() {
// Oracle需要将GROUP BY字段放到SELECT字段中
sqlQuery.setGroupBy("deptno, ename, hiredate");
sqlQuery.setSelect(sqlQuery.getSelect() + ", deptno, ename, hiredate");
}
@Override
public void buildOrderBy() {
sqlQuery.setOrderBy("hiredate");
}
@Override
public void buildLimit() {
sqlQuery.setLimit("10");
}
}
- クライアント
public class Client {
public static void main(String[] args) {
// 创建MySQL建造者
SqlQueryBuilder mySqlQueryBuilder = new MySqlQueryBuilder();
// 创建Oracle建造者
SqlQueryBuilder oracleQueryBuilder = new OracleQueryBuilder();
// 指导者
SqlQueryDirector sqlQueryDirector = new SqlQueryDirector();
// 构建MySQL查询语句
sqlQueryDirector.setSqlQueryBuilder(mySqlQueryBuilder);
sqlQueryDirector.buildSqlQuery("*", "table1");
SqlQuery mySqlQuery = mySqlQueryBuilder.getSqlQuery();
System.out.println("MySQL Query: " + mySqlQuery);
// 构建Oracle查询语句
sqlQueryDirector.setSqlQueryBuilder(oracleQueryBuilder);
sqlQueryDirector.buildSqlQuery("*", "table2");
SqlQuery oracleQuery = oracleQueryBuilder.getSqlQuery();
System.out.println("Oracle Query: " + oracleQuery);
}
}
5. ビルダーモードとファクトリーモードの違い
- ファクトリ パターンは、異なるが関連するタイプのオブジェクト (同じ親クラスまたはインターフェイスを継承するサブクラスのグループ) を作成するために使用され、指定されたパラメータによって作成するオブジェクトのタイプが決定されます。製品を抽象化する-- > 特定の製品
- ビルダー モードは、複雑なオブジェクトのタイプを作成するために使用されます。さまざまなオプションのパラメーターを設定することで、さまざまなオブジェクトを「カスタマイズ」して作成でき、ツール ビルダーを拡張および変更できます。
ソフトウェア設計の基礎
- 最も重要なことは、共通性と変動性を分析することです
デザインパターンの意味
- 抽象化を使用して共通性を修正する (閉包の原理)
- 可変性を具象クラスでカプセル化する (オープン原則)
質問 2: 責任連鎖モデルについて教えてください。責任連鎖モデルはあなたの仕事に役立ちますか?
1. 責任連鎖モデルとは何ですか
責任連鎖のパターン定義
- リクエストの送信者と受信者を結合することを避け、複数のオブジェクトがリクエストを処理できるようにします。
- リクエストを受信するオブジェクトをチェーンに接続し、オブジェクトが処理できるようになるまでリクエストをチェーンに沿って渡します。
2. 責任連鎖モデルの役割
- リクエストとリクエストの処理を分離して、コードのスケーラビリティを向上させます。
3. 責任連鎖モデルの構造
責任連鎖パターンには主に次の役割が含まれます。
- 抽象ハンドラー (ハンドラー) の役割: 抽象処理メソッドとその後の接続を含む、リクエストを処理するためのインターフェースを定義します (チェーン上の各ハンドラーには、次のハンドラーへの参照を保持するメンバー変数があります (上の図の後続ハンドラーなど)。
- Concrete Handler (コンクリート ハンドラー) の役割: 抽象ハンドラーの処理メソッドを実装し、リクエストが処理可能かどうかを判断し、リクエストを処理できる場合は処理し、そうでない場合はリクエストを後続者に転送します。
- クライアントクラス(Client)の役割: 処理チェーンを作成し、その先頭の特定のプロセッサオブジェクトにリクエストを送信しますが、リクエストの処理内容や配送プロセスは問われません。
実際の開発では、この標準的な責任連鎖構造は採用されませんが、これらの特定のプロセッサを管理する責任連鎖マネージャーの追加など、いくつかの変更が加えられることに注意してください。
4. 実際の開発における責任連鎖モデルの適用
SpringBootでは責任連鎖モードを実践する方法がいくつかありますが、今回は主に3つの実践方法を紹介します。
注文プロセスを例として、それを複数の独立した検査ロジックに分割してみましょう。考えられるデータ検証プロセスは次のとおりです。
4.1 実装方法1
- Pojoの作成、オブジェクトの注文
public class OrderContext {
/**
* 请求唯一序列ID
*/
private String seqId;
/**
* 用户ID
*/
private String userId;
/**
* 产品skuId
*/
private Long skuId;
/**
* 下单数量
*/
private Integer amount;
/**
* 用户收货地址ID
*/
private String userAddressId;
}
- プロセッサ インターフェイスを作成するには、標準化された実行のためにデータ処理インターフェイス OrderHandleIntercept を定義します。
public interface OrderHandleIntercept {
/**
* 指定执行顺序
* @return
*/
int sort();
/**
* 对参数进行处理
* @param context
* @return
*/
OrderContext handle(OrderContext context);
}
- 特定のハンドラー クラスを作成するには、3 つの異なるインターフェイス実装クラスを作成し、実行順序を指定します。
/**
* 处理器1 重复下单逻辑验证
* @author zxl
* @date 2023/4/18
**/
@Component
public class RepeatOrderHandleInterceptService implements OrderHandleIntercept {
@Override
public int sort() {
//执行顺序为 1
return 1;
}
@Override
public OrderContext handle(OrderContext context) {
System.out.println("通过seqId,检查客户是否重复下单");
return context;
}
}
/**
* 处理器2: 用于验证请求参数是否合法
* @author zxl
* @date 2023/4/18
**/
@Component
public class ValidOrderHandleInterceptService implements OrderHandleIntercept {
@Override
public int sort() {
//执行顺序为 2
return 2;
}
@Override
public OrderContext handle(OrderContext context) {
System.out.println("检查请求参数是否合法,并且获取客户的银行账户");
return context;
}
}
/**
* 处理器3: /用于检查客户账户余额是否充足
* @author zxl
* @date 2023/4/18
**/
@Component
public class BankOrderHandleInterceptService implements OrderHandleIntercept {
@Override
public int sort() {
//执行顺序为 3
return 3;
}
@Override
public OrderContext handle(OrderContext context) {
System.out.println("检查银行账户是否合法,调用银行系统检查银行账户余额是否满足下单金额");
return context;
}
}
-
プロセッサ チェーン クラスの場合、上記の実装クラスを管理するための注文データ検証マネージャー OrderHandleChainService を作成する必要もあります。
リクエストの処理プロセス全体は HandleChain を通じて管理され、リクエストを送信するクライアントは HandleChain を呼び出して、処理のためにリクエストを HandleChain に渡すだけで済みます。
クライアントにとって、リクエストがどのように処理されるかはわかりません。つまり、リクエスト処理のロジックが変更されたかどうかはクライアントにはわかりません。
/**
* 实现ApplicationContextAware,方便获取Spring容器ApplicationContext,从而可以获取容器内的Bean
* @author zxl
* @date 2023/4/18
**/
@Component
public class OrderHandleChainService implements ApplicationContextAware {
//保存责任链中的处理者
private List<OrderHandleIntercept> handleList = new ArrayList<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//获取指定接口实现类,并按照sort进行排序,放入List
//getBeansOfType这个方法能返回一个接口的全部实现类(前提是所有实现类都必须由Spring IoC容器管理)
Map<String, OrderHandleIntercept> serviceMap = applicationContext.getBeansOfType(OrderHandleIntercept.class);
handleList = serviceMap.values().stream()
.sorted(Comparator.comparing(OrderHandleIntercept::sort))
.collect(Collectors.toList());
}
/**
* 执行处理
*/
public OrderContext execute(OrderContext context){
for (OrderHandleIntercept handleIntercept : handleList) {
context = handleIntercept.handle(context);
}
return context;
}
}
- 単体テストを実行する
@Autowired
private OrderHandleChainService orderHandleChainService;
@Test
public void test02(){
orderHandleChainService.execute(new OrderContext());
}
- 実行結果は以下の通り
通过seqId,检查客户是否重复下单
检查请求参数是否合法,并且获取客户的银行账户
检查银行账户是否合法,调用银行系统检查银行账户余额是否满足下单金额
引き続き検証プロセスや処理プロセスを追加する必要がある場合は、OrderHandleIntercept インターフェイスを再実装するだけで済み、他のコードを変更する必要はありません。
4.2 実装 2
sort() を使用してプロセッサ クラスの実行順序を指定したくない場合は、アノテーションを使用して順序を指定することもできます。
@Order アノテーションを付けてソートを指定する
/**
* 指定注入顺序为1
*
*/
@Order(1)
@Component
public class RepeatOrderHandleInterceptService implements OrderHandleIntercept {
//...省略
}
/**
* 指定注入顺序为2
*
*/
@Order(2)
@Component
public class ValidOrderHandleInterceptService implements OrderHandleIntercept {
//...省略
}
/**
* 指定注入顺序为3
*
*/
@Order(3)
@Component
public class BankOrderHandleInterceptService implements OrderHandleIntercept {
//...省略
}
OrderHandleChainService を変更し、@Autowired を追加して各責任チェーンにオブジェクトを自動的に挿入します。
/**
* 责任链管理类
* 实现ApplicationContextAware,获取IOC容器
* @author zxl
* @date 2023/4/18
**/
@Component
public class OrderHandleChainService {
//保存责任链中的处理者
@Autowired
private List<OrderHandleIntercept> handleList;
/**
* 执行处理
*/
public OrderContext execute(OrderContext context){
for (OrderHandleIntercept handleIntercept : handleList) {
context = handleIntercept.handle(context);
}
return context;
}
}
印刷結果は以下の通りです
检查银行账户是否合法,调用银行系统检查银行账户余额是否满足下单金额
通过seqId,检查客户是否重复下单
检查请求参数是否合法,并且获取客户的银行账户
4.3 実装方法3
抽象クラスを定義するか、上記のケースを例として、責任連鎖設計パターンを実装するには、最初に AbstractOrderHandle などの抽象クラスを定義する必要があります。
/**
* @author zxl
* @date 2023/4/18
**/
public abstract class AbstractOrderHandle {
/**
* 责任链中的下一个节点
*/
private AbstractOrderHandle next;
public AbstractOrderHandle getNext() {
return next;
}
public void setNext(AbstractOrderHandle next) {
this.next = next;
}
/**
* 对参数进行处理, 具体参数拦截逻辑,给子类去实现
* @param orderContext
* @return: com.mashibing.designboot.responsibility.pojo.OrderContext
*/
public abstract OrderContext handle(OrderContext orderContext);
/**
* 执行入口
* @param context
* @return: com.mashibing.designboot.responsibility.pojo.OrderContext
*/
public OrderContext execute(OrderContext context){
//每个处理器 要执行的处理逻辑
context= handle(context);
//判断是否还有下一个责任链节点,没有的话,说明是最后一个节点
if(getNext() != null){
getNext().execute(context);
}
return context;
}
}
次に、処理クラスを3つずつ作成し、通し番号を並べます。
@Component
@Order(1)
public class RepeatOrderHandle extends AbstractOrderHandle {
@Override
public OrderContext handle(OrderContext context) {
System.out.println("通过seqId,检查客户是否重复下单");
return context;
}
}
@Component
@Order(2)
public class ValidOrderHandle extends AbstractOrderHandle {
@Override
public OrderContext handle(OrderContext context) {
System.out.println("检查请求参数,是否合法,并且获取客户的银行账户");
return context;
}
}
@Component
@Order(3)
public class BankOrderHandle extends AbstractOrderHandle {
@Override
public OrderContext handle(OrderContext context) {
System.out.println("检查银行账户是否合法,调用银行系统检查银行账户余额是否满足下单金额");
return context;
}
}
OrderHandleManager などの一連の責任マネージャーを作成する
@Component
public class OrderHandleManager {
@Autowired
private List<AbstractOrderHandle> orderHandleList;
/**
* 实现Bean初始化之前的自定义操作
* Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的初始化方法)
* @PostConstruct注解的功能:当依赖注入完成后用于执行初始化的方法,并且只会被执行一次
* @return: null
*/
@PostConstruct
public void initChain(){
int size = orderHandleList.size();
for (int i = 0; i < size; i++) {
if(i == size -1){
//责任链上,最后一个处理者
orderHandleList.get(i).setNext(null);
} else {
//进行链式连接
orderHandleList.get(i).setNext(orderHandleList.get(i + 1));
}
}
}
/**
* 执行处理
* @param context
* @return: com.mashibing.designboot.responsibility.pojo.OrderContext
*/
public OrderContext execute(OrderContext context){
OrderContext execute = orderHandleList.get(0).execute(context);
return context;
}
}
テスト
@Autowired
private OrderHandleManager orderHandleManager;
@Test
public void test02(){
// orderHandleChainService.execute(new OrderContext());
orderHandleManager.execute(new OrderContext());
}
実行結果は期待と一致しています。
通过seqId,检查客户是否重复下单
检查请求参数,是否合法,并且获取客户的银行账户
检查银行账户是否合法,调用银行系统检查银行账户余额是否满足下单金额
以上、SpringBootがどのように責任連鎖モードを導入するのかを説明し、3つの実装方法を紹介しました。
2 番目のタイプが最も多く使用され、次に 2 番目のタイプ、そして 3 番目のタイプはあまり使用されません。3 番目のタイプは本質的に連鎖記述法ですが、最初のタイプほど直感的に理解できるわけではありません。可読性はチェックされていますが、効果は同じです。
5. 責任連鎖モデルの概要
1) 責任連鎖モデルの利点:
- オブジェクト間の結合の減少
- オブジェクトへの責任の委任における柔軟性の向上
- オブジェクト間の接続が簡素化され、責任の共有がより明確になります。
2) 責任連鎖モデルの欠点:
- リクエストが処理されないという保証はありません
- システムのパフォーマンスに影響する可能性があります
- クライアントの複雑さの増加
3) シーン分析を使用する
- 複数のオブジェクトを使用して実行時にリクエストを処理するには
- ユーザーには具体的な処理ロジックを知られたくないのです。
質問 3: オブザーバー モードとパブリッシュ/サブスクライブ モードの違いは何ですか? 仕事でオブザーバー モードを使用したことがありますか?
1. 観察者パターン
1.1 オブザーバーパターンとは
オブザーバー モードは、オブジェクト間の依存関係を確立するために使用され、オブジェクトが変更されると、自動的に他のオブジェクトに通知され、他のオブジェクトもそれに応じて応答します。
オブザーバー モードでは、次の役割があります。
- サブジェクト: 抽象サブジェクト (抽象観察)、抽象サブジェクトの役割はコレクション内のすべてのオブザーバー オブジェクトを保存します。各サブジェクトは任意の数のオブザーバーを持つことができ、抽象サブジェクトはインターフェイスを提供し、オブザーバー オブジェクトを追加および削除できます。
- ConcreteSubject: 特定のサブジェクト (特定のオブザーバー)。このロールは、関連する状態を特定のオブザーバー オブジェクトに保存し、特定のサブジェクトの内部状態が変化したときに、登録されているすべてのオブザーバーに通知を送信します。
- オブザーバー: 抽象オブザーバーはオブザーバーの抽象クラスであり、テーマの変更が通知されたときに自身を更新するように更新インターフェイスを定義します。
- ConcrereObserver: 具体的なオブザーバー。トピックの変更が通知されたときに自身の状態を更新するために、抽象オブザーバーによって定義された更新インターフェイスを実装します。特定のオブザーバー内の特定のターゲット オブジェクトへの参照を維持します。これには、特定のオブザーバーの関連状態が保存されます。これらの状態は、特定のターゲットと一致している必要があります。
1.2 オブザーバーモードの実装
- 観察者
/**
* 抽象观察者
* @author zxl
* @date 2022/10/11
**/
public interface Observer {
//update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现
public void update();
}
/**
* 具体观察者
* @author zxl
* @date 2022/10/11
**/
public class ConcreteObserverOne implements Observer {
@Override
public void update() {
//获取消息通知,执行业务代码
System.out.println("ConcreteObserverOne 得到通知!");
}
}
/**
* 具体观察者
* @author zxl
* @date 2022/10/11
**/
public class ConcreteObserverTwo implements Observer {
@Override
public void update() {
//获取消息通知,执行业务代码
System.out.println("ConcreteObserverTwo 得到通知!");
}
}
- 観察された
/**
* 抽象目标类
* @author zxl
* @date 2022/10/11
**/
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
/**
* 具体目标类
* @author zxl
* @date 2022/10/11
**/
public class ConcreteSubject implements Subject {
//定义集合,存储所有观察者对象
private ArrayList<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() {
//遍历观察者集合,调用每一个观察者的响应方法
for (Observer obs : observers) {
obs.update();
}
}
}
- テストクラス
public class Client {
public static void main(String[] args) {
//创建目标类(被观察者)
ConcreteSubject subject = new ConcreteSubject();
//注册观察者类,可以注册多个
subject.attach(new ConcreteObserverOne());
subject.attach(new ConcreteObserverTwo());
//具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
subject.notifyObservers();
}
}
2. パブリッシュ・サブスクライブ・モードとオブザーバー・モードの違い
2.1 定義の違い
パブリッシュ・サブスクライブ・モデルは、広義にはオブザーバー・モデルに属します。
- パブリッシュ/サブスクライブ モードは、オブザーバー モードの最も一般的に使用される実装であり、分離と再利用の観点からは、一般的なオブザーバー モードよりも優れています。
2.2 両者の違い
オブザーバー モードとパブリッシュ/サブスクライブ モードの構造的な違いを見てみましょう。
操作プロセスの違い
- オブザーバー モード:データ ソースはサブスクライバーに変更を直接通知します。
- パブリッシュ/サブスクライブ モード:データ ソースは、変更が発生したことをサード パーティ (イベント チャネル) に伝え、サード パーティはサブスクライバに変更が発生したことを通知します。
3. 実際の開発におけるオブザーバーモードの適用
3.1 実際の開発における需要シナリオ
私たちの日々のビジネス開発において、オブザーバー モードはビジネスのデカップリングを実現する上で大きな役割を果たします。ユーザー登録シナリオを例に挙げると、次の図に示すように、ユーザー登録が完了したら、ユーザーにメールやクーポンなどを送信する必要があるとします。
オブザーバー パターンを使用した後
- UserService が独自のユーザー登録ロジックを完了した後は、他の拡張ロジックに注意を払うことなく、UserRegisterEvent イベントを発行するだけで済みます。
- 他のサービスはUserRegisterEvent イベントをサブスクライブして、カスタム拡張ロジックを実装できます。
3.2 春イベントの仕組み
Spring はオブザーバー パターンに基づいて、次の 3 つの部分で構成される独自のイベント メカニズムを実装します。
- Event
ApplicationEvent
:カスタムイベントを継承して実装します。さらに、イベントソースはそのsource
プロパティ取得でき、発生時刻はプロパティを通じて取得できます。timestamp
- イベント発行者
ApplicationEventPublisher
: それを通じてイベントを発行できます。 - イベントリスナー
ApplicationListener
:実装することで、指定した種類のイベントを監視します。
3.3 コードの実装
(1) UserRegisterEvent
- UserRegisterEvent イベント クラスを作成し、ApplicationEvent クラスを継承して、ユーザーのイベントを登録します。コードは以下のように表示されます:
/**
* 用户注册事件
* @author zxl
* @date 2023/4/19
**/
public class UserRegisterEvent extends ApplicationEvent {
/**
* 用户名
*/
private String username;
public UserRegisterEvent(Object source) {
super(source);
}
public UserRegisterEvent(Object source,String username) {
super(source);
this.username = username;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
(2) UserService(イベントソース+イベント発行)
- 次のコードを使用して UserService クラスを作成します。
/**
* 事件源角色+事件发布
* @author zxl
* @date 2023/4/19
**/
@Service
public class UserService implements ApplicationEventPublisherAware {
private Logger logger = LoggerFactory.getLogger(getClass());
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void register(String username){
//... 执行注册逻辑
logger.info("[register][执行用户{}的注册逻辑]",username);
/**
* publishEvent方法, 参数是: ApplicationEvent的实现类对象
* 每当事件发布时,所有的ApplicationListener就会被自动的触发.
*/
//... 发布用户注册事件
applicationEventPublisher.publishEvent(new UserRegisterEvent(this,username));
}
}
ApplicationEventPublisherAware
インターフェースを実装し、ApplicationEventPublisher
それに注入します。- 登録ロジックを実行した後、
ApplicationEventPublisher
のpublishEvent(ApplicationEvent event)
メソッドを呼び出してUserRegisterEvent
イベント。 - イベント メカニズムの実装には、イベント ソース、イベント、イベント リスナーの 3 つの部分が必要です。上記の ApplicationEvent はイベントに相当し、ApplicationListener はイベント リスナーに相当します。ここでのイベント ソースとは、ApplicationEventPublisher を指します。 。
(3) 電子メールサービスの作成
/**
* 事件监听角色
* @author zxl
* @date 2023/4/19
**/
@Service //实现 ApplicationListener 接口,通过 E 泛型设置感兴趣的事件。
public class EmailService implements ApplicationListener<UserRegisterEvent> {
private Logger logger = LoggerFactory.getLogger(getClass());
//实现 #onApplicationEvent(E event) 方法,针对监听的 UserRegisterEvent 事件,进行自定义处理。
@Override
public void onApplicationEvent(UserRegisterEvent event) {
logger.info("[onApplicationEvent][给用户({}) 发送邮件]", event.getUsername());
}
}
- ApplicationListener インターフェイスを実装し、
E
ジェネリックス。 - ApplicationListener インターフェースを実装した後、監視対象の UserRegisterEvent イベントに対してカスタム処理を実行するメソッド onApplicationEvent() を実装する必要があります。このメソッドは、コンテナがすべての Bean を初期化した後に実行されます。
(4) クーポンサービス
@Service
public class CouponService {
private Logger logger = LoggerFactory.getLogger(getClass());
//添加 @EventListener 注解,并设置监听的事件为 UserRegisterEvent。
@EventListener
public void addCoupon(UserRegisterEvent event) {
logger.info("[addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
}
}
(5) デモコントローラー
/demo/register
登録インターフェースを提供する
@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
private UserService userService;
@GetMapping("/register")
public String register(String username) {
userService.register(username);
return "success";
}
}
3.4 コードのテスト
① DemoApplication クラスを実行してプロジェクトを開始します。
② http://127.0.0.1:8080/demo/register?username=mashibing インターフェースを呼び出して登録します。IDEA コンソールは次のようにログを出力します。
// UserService 发布 UserRegisterEvent 事件
2023-04-19 16:49:40.628 INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.service.UserService : [register][执行用户mashibing的注册逻辑]
//EmailService 监听处理该事件
2023-04-19 16:49:40.629 INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.EmailService : [onApplicationEvent][给用户(mashibing) 发送邮件]
//CouponService 监听处理该事件
2023-04-19 16:49:40.629 INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.CouponService : [addCoupon][给用户(mashibing) 发放优惠劵]
4. オブザーバーパターンの概要
1) オブザーバーパターンの利点
- ターゲットクラスとオブザーバー間の結合を減らす
- ブロードキャストメカニズムを実装可能
2) オブザーバーパターンのデメリット
- 通知の送信には一定の時間がかかります
- オブザーバーには循環依存関係があり、システムクラッシュにつながる可能性があります
*3) オブザーバーモードの一般的な使用シナリオ
- 1 つのオブジェクトが変更されると、他のオブジェクトも変更する必要があります
- オブジェクトが変更された場合は通知する必要がある
質問 4: アダプター パターンを実際の作業に適用するにはどうすればよいですか?
1. アダプターモードの概要
1.1 アダプターモードの概要
アダプター パターン (アダプター パターン) の本来の定義は、クラスのインターフェイスを顧客が期待する別のインターフェイスに変換することであり、アダプターは互換性のない 2 つのクラスを連携させることができます。
アダプターモードの主な機能は、元々互換性のなかったインターフェイスを適応と修正を通じて統合し、ユーザーが使いやすくすることです。先ほど述べたユニバーサル充電とマルチインターフェイスデータラインと同様に、それらはすべてさまざまなインターフェイスに適応するためのものです。互換性があります。
なぜインターフェースを切り替えるのか?
- 元のインターフェイスとターゲット インターフェイスの両方がすでに存在しており、インターフェイスのコードを変更するのは簡単ではありません
- 抽象インターフェイスは、既存のコンポーネントのロジックを再利用することを望んでいます。
1.2 アダプタパターン構造
アダプター パターン (アダプター) には、次の主な役割が含まれます。
- ターゲット (Target) インターフェイス: 現在のシステム ビジネスによって予期されるインターフェイス。抽象クラスまたはインターフェイスにすることができます。
- アダプター (Adaptee) クラス: アダプターは適応される役割であり、アクセスして適応される既存のコンポーネント ライブラリ内のコンポーネント インターフェイスです。
- アダプタ(Adapter)クラス:アダプタオブジェクトを継承または参照することでアダプタインタフェースをターゲットインタフェースに変換し、顧客がターゲットインタフェースの形式でアダプタにアクセスできるようにするコンバータです。
アダプターのパターンは次のように分かれています。
-
クラスアダプター
-
オブジェクトアダプター
両者の違いは、アダプターとアダプターの関係であり、クラスアダプターは継承関係、オブジェクトアダプターは集約関係です。設計原則によれば、集約は継承よりも優先され、オブジェクトアダプターはもっと使った。
2. 実際の開発におけるアダプタモードの適用
2.1 要件の説明
システムの速度を向上させるために、一部のデータは KV の形式でメモリにキャッシュされ、プラットフォームは get、put、remove などの API および関連する管理メカニズムを提供します。
HashMap から Memcached、そして Redis に至る関数実現の反復プロセスでは、後で新しいキャッシュ コンポーネントを追加するときに自由に切り替えられ、オープンとクローズの原則に準拠していることを保証する必要があります。
設計に関する質問:
- 開閉原則の遵守を前提とした機能拡張をどう実現するか
- 2 つのクライアント API は異なります。自由に切り替えられるようにする方法
アダプターパターンを使用する
2.2 機能実現
アダプター モードを使用すると、同様の機能を持つさまざまなサードパーティ コンポーネント (実装スキーム) を必要な API に統合できます。ビジネス コードは、サードパーティ API ではなく、統合 API にのみ依存します。
(1) まず、get、put、remove などの操作メソッドを含むキャッシュ インターフェイスを定義します。例えば:
public interface Cache {
void put(String key, Object value);
Object get(String key);
void remove(String key);
}
(2) 次に、HashMap、Memcached、Redis の 3 つのキャッシュ スキームにそれぞれ対応する、インターフェイスの 3 つのアダプターを実装します。例えば:
public class HashMapCacheAdapter implements Cache {
private Map<String, Object> cache = new HashMap<>();
@Override
public void put(String key, Object value) {
cache.put(key, value);
}
@Override
public Object get(String key) {
return cache.get(key);
}
@Override
public void remove(String key) {
cache.remove(key);
}
}
public class MemcachedCacheAdapter implements Cache {
private MemcachedClient memcachedClient;
public MemcachedCacheAdapter(MemcachedClient memcachedClient) {
this.memcachedClient = memcachedClient;
}
@Override
public void put(String key, Object value) {
memcachedClient.set(key, 0, value);
}
@Override
public Object get(String key) {
return memcachedClient.get(key);
}
@Override
public void remove(String key) {
memcachedClient.delete(key);
}
}
public class RedisCacheAdapter implements Cache {
private Jedis jedis;
public RedisCacheAdapter(Jedis jedis) {
this.jedis = jedis;
}
@Override
public void put(String key, Object value) {
jedis.set(key, value.toString());
}
@Override
public Object get(String key) {
return jedis.get(key);
}
@Override
public void remove(String key) {
jedis.del(key);
}
}
最後に、構成ファイル内の構成に従って、対応するキャッシュ アダプターを作成するためのファクトリ クラスが必要です。例えば:
public class CacheAdapterFactory {
public static Cache createCacheAdapter(String type) {
if ("HashMap".equals(type)) {
return new HashMapCacheAdapter();
} else if ("Memcached".equals(type)) {
MemCachedClient memCachedClient = new MemCachedClient();
return new MemcachedCacheAdapter(memCachedClient);
} else if ("Redis".equals(type)) {
Jedis jedis = new Jedis("localhost", 6379);
return new RedisCacheAdapter(jedis);
} else {
throw new IllegalArgumentException("Invalid cache type: " + type);
}
}
}
使用する場合は、ファクトリ クラスの createCacheAdapter メソッドを呼び出し、キャッシュ タイプを渡して対応するキャッシュ アダプターを取得するだけです。例えば:
Cache cache = CacheAdapterFactory.createCacheAdapter("Redis");
cache.put("key", "value");
Object result = cache.get("key");
cache.remove("key");
3. アダプターモードの概要
1) アダプターモードのメリット
- 無関係な 2 つのクラスを一緒に実行できるようにします
- クラスの再利用性を向上させ、複数の異なるインターフェースを統合できる
- 既存のインターフェースの実装クラスを非表示にする
- 柔軟性が高く、自由に適応可能
2) アダプターモードのデメリット
- クラス アダプターを使用すると、一度に最大 1 つのアダプター クラスを適応できます。
- アダプターを過度に使用すると、システムが複雑になります。
3) アダプターモードが適用されるシナリオ
- 複数のクラスのインターフェースを統合する
- 元のインターフェイスを変更できないが、互換性が必要な場合
質問 5: デコレータ モードとプロキシ モードの違いを教えてください? 作業でデコレータ モードを使用するにはどうすればよいですか?
1. それぞれの定義
デコレータ モード: デコレータ モードは、元のクラスのインターフェイスを変更せずに元のクラスの機能を強化し、複数のデコレータのネストされた使用をサポートします。
- コンポーネント抽象コンポーネントの役割: 具体コンポーネントと抽象装飾クラスの親クラスであり、クライアントが装飾されていないオブジェクトと装飾されたオブジェクトを一貫した方法で処理できるように、具体コンポーネントに実装されるビジネス メソッドを宣言します。
- 具体コンポーネント 具体コンポーネントの役割: これは、抽象コンポーネント クラスのサブクラスであり、特定の構築オブジェクトを定義し、抽象構築で宣言されたメソッドを実装します。デコレータ クラスは、追加の責任 (メソッド) を追加できます。
- Decorator 抽象装飾ロール: 抽象コンポーネント クラスのサブクラスであり、特定のコンポーネントに責任を追加し、装飾の目的を達成するために抽象コンポーネント オブジェクトへの参照を維持するために使用されます。
- 具体的なデコレータ 具体的な装飾の役割: これは、抽象装飾クラスのサブクラスであり、コンポーネントに新しい責任を追加する役割を果たします。各具体的な装飾クラスは、定義されたメソッドを呼び出したり、新しいメソッドを追加したりできるいくつかの新しい動作を定義します。
コード例
/**
* 抽象构件类
* @author zxl
* @date 2022/9/27
**/
public abstract class Component {
//抽象方法
public abstract void operation();
}
/**
* 具体构建类
* @author spikeCong
* @date 2022/9/27
**/
public class ConcreteComponent extends Component {
@Override
public void operation() {
//基础功能实现(复杂功能通过装饰类进行扩展)
}
}
/**
* 抽象装饰类-装饰者模式的核心
* @author zxl
* @date 2022/9/27
**/
public abstract class Decorator extends Component{
//维持一个对抽象构件对象的引用
private Component component;
//注入一个抽象构件类型的对象
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
//调用原有业务方法(这里并没有真正实施装饰,而是提供了一个统一的接口,将装饰过程交给子类完成)
component.operation();
}
}
/**
* 具体装饰类
* @author zxl
* @date 2022/9/27
**/
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
@Override
public void operation() {
super.operation(); //调用原有业务方法
addedBehavior(); //调用新增业务方法
}
//新增业务方法
public void addedBehavior(){
//......
}
}
プロキシモード: プロキシモードは、元のクラスのインターフェースを変更せずに、元のクラスのプロキシクラスを定義します. 主な目的は、機能拡張ではなく、アクセス制御です. これがデコレータモードとの最大の違いです.
2. 主な違い
目的が違う
- プロキシ モード: コントロール —> 自分用
- デコレータ パターン: 拡張 —> ターゲット クラス用
使い方の違い
- プロキシ モード: プロキシされたオブジェクトを完全に制御し、実行できるかどうかを決定します。
- デコレーター モード: 制御なしで確実に実行され、装飾関数のレイヤーが追加されます。
クライアントのために
- プロキシ モード: プロキシ オブジェクトの機能を重視します。
- デコレータ モード: デコレータの機能強化に重点を置いています。
3. 実際の開発におけるデコレータパターンの適用
要件: 複数の装飾がある場合、前の装飾に基づいて後の装飾が確実に装飾されるようにする方法。たとえば、文字を暗号化して圧縮する必要があります。圧縮、暗号化に基づいた圧縮を行うにはどうすればよいですか?
- キャラクターコンポーネントインターフェースを作成する
public interface StringComponent {
String transform(String str);
}
- キャラクターコンポーネントを実装する
//字符组件
public class StringEncryptor implements StringComponent {
@Override
public String transform(String str) {
//base64编码
String encoderStr = Base64.getEncoder().encodeToString(str.getBytes());
return encoderStr;
}
}
- 文字暗号化デコレータ
public class StringEncryptorDecorator implements StringComponent {
private StringComponent component;
public StringEncryptorDecorator(StringComponent component) {
this.component = component;
}
@Override
public String transform(String str) {
String encrypted = component.transform(str); // 先调用前面一个装饰器或组件的方法
// 这里演示一个简单的压缩方法,将字符串压缩成一行
return encrypted.replaceAll("\\s+", "");
}
}
- 文字圧縮用のデコレーター
public class StringCompressorDecorator implements StringComponent {
private StringComponent component;
public StringCompressorDecorator(StringComponent component) {
this.component = component;
}
@Override
public String transform(String str) {
String compressed = component.transform(str); // 先调用前面一个装饰器或组件的方法
// 这里演示一个简单的压缩方法,将字符串压缩成一行
return compressed.replaceAll("\\s+", "");
}
}
- クライアント
public class Client {
public static void main(String[] args) {
StringComponent component = new StringEncryptor(); // 创建字符加密组件
component = new StringEncryptorDecorator(component); // 用字符加密装饰器装饰它
component = new StringCompressorDecorator(component); // 用字符压缩装饰器再次装饰它
String original = "Hello, world!"; // 原始字符串
String transformed = component.transform(original); // 转换后的字符串
System.out.println(transformed);
}
}
出力は次のようになります。Ifmmp-!xpsme"
これは、元の文字列を暗号化して圧縮した結果です。圧縮演算が暗号化演算に基づいて実行されることがわかります。
4. デコレータパターンの概要
1) デコレータ パターンの利点:
- デコレータ パターンは、継承よりも柔軟です。
- 複数回装飾することができ、装飾順序が異なると異なる動作を実現できます。
2) デコレータ モードの欠点:
- 小さなオブジェクトが多すぎます
- デコレータ モード、エラーが発生しやすくなります
3) デコレーターパターンの適用シナリオ
- 動的拡張
- 拡張クラスを継承するシナリオはサポートされていません