デザインパターン 07 - 責任連鎖パターン

責任連鎖パターンは動作設計パターンであり、一般的なフィルター チェーンは責任連鎖パターンを使用して設計されます。

1. 実際の開発シナリオにおける問題の紹介

Q: 合計 3 つのレベルを持つレベルブレイク ゲームがあるとします。各レベルが合格条件に達した場合にのみ、次のレベルに進むことができます。これは Java を使用して実装されています。
A: この問題に対して、単純な考え方によれば、第 1 レベル、第 2 レベル、第 3 レベルの 3 つのカテゴリを定義できます。クライアントがゲームを開始すると、最初に第 1 レベルに入り、次に第 3 レベルに入ります。最初のレベルを通過した後、3 番目のレベル、2 番目のレベル、というように続きます。そのようなコードは次のように取得できます。

@Slf4j
public class FirstLevel {
    
    
    @Autowired
    private SecondLevel secondLevel;
    public void play(){
    
    
        log.info("进入第一关");
        //游戏操作,获得分数
        int res = 80;
        if(res >= 80){
    
    
            secondLevel.play();
        }else {
    
    
            return;
        }
    }
}
@Component
@Slf4j
public class SecondLevel {
    
    
    @Autowired
    private ThirdLevel thirdLevel;
    public void play(){
    
    
        log.info("进入第二关");
        //游戏操作,获得分数
        int res = 90;
        if(res >= 90){
    
    
            thirdLevel.play();
        }else {
    
    
            return;
        }
    }
}
@Component
@Slf4j
public class ThirdLevel {
    
    
    public void play(){
    
    
        log.info("进入第三关");
        //游戏操作,获得分数
        int res = 95;
        if(res >= 95){
    
    
            log.info("游戏通关");
        }else {
    
    
            return;
        }
    }
}

@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
class DpApplicationTests {
    
    
    @Autowired
    private FirstLevel firstLevel;

    @Test
    public void client(){
    
    
        firstLevel.play();
    }
}

上記のコードのどこが間違っているのでしょうか?

  • 結合が強すぎます。各クラスに他のクラスが含まれています
  • レベルの順序を変更したい場合は、クラスの内部コードを変更する必要があります

2. 責任連鎖モデルの説明

責任連鎖モデルの適用シナリオは次のとおりです: リクエストは多くのプロセッサーで処理でき、これらのプロセッサーがチェーンを形成します。最終的に誰がリクエストを処理するか、またはリクエストを処理するかどうかに関係なく、リクエストはチェーンに送信するだけで済みます。チェーンに付いています。これにより、要求者と要求者の分離が実現され、同時にチェーンを定義し、プロセッサーをチェーン上に配置し、プロセッサーの順序を定義するだけで済み、プロセッサー同士が保持する必要はありません。

2.1 コアクラスとクラス図

ここに画像の説明を挿入します

抽象プロセッサ インターフェイスと 3 つの具象プロセッサがあります。プロセッサ チェーン クラスには 3 つの具象プロセッサを含むリストがあります。プロセッサ チェーン オブジェクトは、クライアントでリクエストと対話するために使用されます。

2.2 基本コード

  • 1 処理結果データ (タイプ) とチェーン上で伝播を継続するかどうかを含む、プロセッサーの戻り結果タイプを定義します。
@Data
public class ProcessResult<R> {
    
    
    private boolean next;
    private R data;

    public ProcessResult(boolean next, R data) {
    
    
        this.next = next;
        this.data = data;
    }

    /**
     * 继续处理并返回数据
     * @param data
     * @param <R>
     * @return
     */
    public static <R> ProcessResult resumeData(R data){
    
    
        return new ProcessResult(true,data);
    }

    /**
     * 继续处理不返回数据
     * @param <R>
     * @return
     */
    public static <R> ProcessResult resumeNoData(){
    
    
        return new ProcessResult(true,null);
    }

    /**
     * 不继续处理+返回数据
     * @param <R>
     * @return
     */
    public static <R> ProcessResult stopData(R data){
    
    
        return new ProcessResult(false,data);
    }

    /**
     * 不继续处理+不返回数据
     * @param <R>
     * @return
     */
    public static <R> ProcessResult stopNoData(){
    
    
        return new ProcessResult(false,null);
    }
}

  • 2 抽象プロセッサを定義し、戻り値は上記で定義した型になります。パラダイムは 2 つあり、1 つはプロセッサが受け取るパラメータ、もう 1 つはプロセッサが返す特定のデータ (ProcessResult にラップされる) の型です。
public interface Processor<Result,Param> {
    
    
    /**
     * 使用范型的方法、类,需要指定范型
     * @param param 处理器参数
     * @return Res 返回结果
     */
    ProcessResult<Result> process(Param param );


    /**
     * 接口的默认方法,表示该处理器是否已经处理过请求
     * @return
     */
    default boolean isProcessed(){
    
    
        return false;
    }
}
  • 3. リクエストを処理するための 3 つの特定のプロセッサを定義し、順序アノテーションを使用して JavaBean のスキャン順序を指定します。プロセッサを実装する場合は、汎用パラメータの指定に注意してください。
@Component
@Order(1)
@Slf4j
public class ConcreteProcessor01 implements Processor<String,String>{
    
    
    @Override
    public ProcessResult<String> process(String s) {
    
    
        //通过参数判断是不是处理请求
        log.info("ConcreteProcessor01处理");
        //处理
        return ProcessResult.resumeData(s);
    }
}
@Component
@Order(2)
@Slf4j
public class ConcreteProcessor02 implements Processor<String,String>{
    
    
    @Override
    public ProcessResult<String> process(String s) {
    
    
        //通过参数判断是不是处理请求
        log.info("ConcreteProcessor02处理");
        //处理
        return ProcessResult.stopData(s);
    }
}
@Component
@Order(3)
@Slf4j
public class ConcreteProcessor03 implements Processor<String,String>{
    
    
    @Override
    public ProcessResult<String> process(String s) {
    
    
        //通过参数判断是不是处理请求
        log.info("ConcreteProcessor03处理");
        //处理
        return ProcessResult.resumeData(s);
    }
}
  • 4 プロセッサ チェーンを定義し、コンストラクターを List に挿入します。

    パラメータを入力すると、Spring コンテナはすべての P タイプ Bean をスキャンし、それらをリストに追加します。Processor<Result,Param> は、前に定義した ConcreteProcessor01、02、および 03 (つまり、Processor<String,String>) と一致しますが、BaseChain の汎用性を確保するために、異なる種類のプロセッサを集約する複数のプロセッサ チェーンを使用できます。どこでもジェネリックを使用します。

@Component
@Slf4j
public class BaseChain<Result,Param> {
    
    
    private List<Processor<Result,Param>> processors;

    @Autowired
    public BaseChain(List<Processor<Result,Param>> processors) {
    
    
        this.processors = processors;
    }

    public void doProcess(Param param) {
    
    
        for (Processor processor : processors) {
    
    
            ProcessResult process = processor.process(param);
            if (process.getData() != null) {
    
    
                log.info(process.getData().toString());
            }
            if (!process.isNext()) {
    
    
                return;
            }
        }
    }
}
  • クライアント5名
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
class DpApplicationTests {
    
    
    @Autowired
    private BaseChain baseChain;

    @Test
    public void chainTest(){
    
    
        baseChain.doProcess("123");
    }
}

出力:
ここに画像の説明を挿入します

3. ビルダー パターンを使用して問題を解決する

1の問題は2のコードを適用することで解決できます

4. 責任連鎖モデルの適用例

Spring Web の HandlerInterceptor

HandlerInterceptor インターフェイスは、Web 開発で非常に一般的に使用されます。このインターフェイスには、preHandle()、postHandle()、および afterCompletion() という 3 つのメソッドがあります。preHandle() メソッドはリクエストが処理される前に呼び出され、postHandle() メソッドが呼び出されます。リクエストが処理された後、ビュー内で実行されます。レンダリング前に呼び出され、ビューのレンダリングが完了した後に afterCompletion() メソッドが呼び出されます。

public interface HandlerInterceptor {
    
    
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
    
    

		return true;
	}

	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
    
    
	}

	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
    
    
	}
}

このようなフィルターは多数ありますが、責任チェーン モードを使用してこれらのフィルターをチェーン (HandlerExecutionChain) に配置すると、HandlerExecutionChain がすべての HandlerInterceptors を呼び出します。

public class HandlerExecutionChain {
    
    

	...

    //在数组中存放所有的过滤器
	@Nullable
	private HandlerInterceptor[] interceptors;
	//数组的下标
	private int interceptorIndex = -1;
	//pre的调度
	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
    
    
			for (int i = 0; i < interceptors.length; i++) {
    
    
			//遍历过滤器
				HandlerInterceptor interceptor = interceptors[i];
        //preHandle返回false,进入该if,结束处理流程,
				if (!interceptor.preHandle(request, response, this.handler)) {
    
    
          //执行渲染完成后的逻辑
					triggerAfterCompletion(request, response, null);
					return false;
				}
        //修改当前指向的过滤器下标,记录最后一个成功执行preHandle()方法的拦截器的索引。
        //如果某个拦截器的preHandle()方法返回false,则遍历将停止,并且请求处理也将停止。
        //在这种情况下,HandlerExecutionChain需要调用所有已成功执行preHandle()方法的拦截器的afterCompletion()方法。
        //这时候就需要使用interceptorIndex得到最后一个成功执行的preHandle方法所在的拦截器在拦截链的位置。
				this.interceptorIndex = i;
			}
		}
		return true;
	}
}

5. まとめ

5.1 解決された問題

責任チェーン上のプロセッサはリクエストの処理を担当します。クライアントはリクエストを責任チェーンに送信するだけでよく、リクエストの処理の詳細やリクエストの配信について気にする必要はありません。したがって、責任チェーンはリクエストの送信者を分離します。およびリクエストプロセッサ。

5.2 使用シナリオ

メッセージを処理するときにフィルタリングするチャネルが多数あります。

5.3 利点と欠点

利点: 1. 結合度を低減します。これにより、リクエストの送信者と受信者が分離されます。2. 単純化されたオブジェクト。そのため、オブジェクトはチェーンの構造を知る必要がありません。3. オブジェクトに対する責任の割り当ての柔軟性を強化します。チェーン内のメンバーを変更したり、順序を移動したりすることで、責任を動的に追加または削除できます。4. 新しいリクエスト処理クラスを追加すると非常に便利です。

欠点: 1. リクエストが受信されるという保証はありません。2. システムのパフォーマンスにある程度の影響があり、コードのデバッグが不便になり、ループ呼び出しが発生する可能性があります。3. 実行時の特性を観察することが難しく、デバッグに支障をきたす可能性があります。


参考:
[1]実践的な戦闘: 設計パターンの責任連鎖設計パターンの詳細分析
[2] Spring での責任連鎖パターンの適用
[3]責任連鎖パターン - 初心者向けチュートリアル

おすすめ

転載: blog.csdn.net/baidu_40120883/article/details/131642409