責任デザインのパターンの連鎖

20660 ワード

責任の連鎖は、イベント/リクエスト/コマンド/クエリを疎外結合オブジェクトの連鎖に伝播する機能を提供する行動デザインパターンです.現代C +の責任デザインパターンのチェーンは、ハンドラのチェーンに沿ってリクエストを渡すことができます&要求を受信すると、各ハンドラーは、要求を処理するか、チェーン内の次のハンドラーに転送するかを決定します.

/!\: This article has been originally published on my blog. If you are interested in receiving my latest articles, please sign up to my newsletter.


ところで、あなたが行動デザインパターンに関する私の他の記事をチェックしていないならば、リストはここにあります
  • Chain of responsibility
  • Command
  • Interpreter
  • Iterator
  • Mediator
  • Memento
  • Observer
  • State
  • Strategy
  • Template Method
  • Visitor
  • あなたが記事のこのシリーズを通して見るコード断片は洗練されていない簡素化されます.だから、あなたはよく私のようなキーワードを使用しないを参照してくださいoverride , final , public (継承時)コードをコンパクトにして、単一の標準的なスクリーンサイズで(大部分の時間)を消耗させること.私も好むstruct の代わりにclass 書くだけで線を節約するためにpublic: 「時々、またミスvirtual destructor , コンストラクタcopy constructor , 接頭辞std:: , 動的なメモリの削除、意図的に.私はまた、標準的な方法やJargonsを使うよりも、最も簡単な方法でアイデアを伝えたいという実用的な人を考えています.
    注意:
  • あなたが直接ここでつまずくならば、私はあなたが行くことを提案しますWhat is design pattern? 第一に、たとえ些細なことであっても.私は、それがあなたがこの話題でより多くを調査するのを奨励すると思っています.
  • 記事のこのシリーズで遭遇するこのコードのすべては、C++ 20を使用してコンパイルされますModern C++ 機能はC++ほとんどの場合には17.ですから、最新のコンパイラにアクセスできないのならばhttps://wandbox.org/ を使用すると、同様にブーストライブラリをpreinstallしています.

  • 意図

    To provide the chance to handle the request by more than one object/component.

  • 責任デザインパターンの連鎖は、すべての機会をコマンド/クエリを処理する機会を疎結合オブジェクトのチェーンです.そして、それらは若干の種類のデフォルト処理実施を有することができる、そして/または、また、それらは処理チェーンを終了させることができて、それによって、オブジェクトの残りにイベントの伝播を予防する.
  • 言い換えると、あなたがちょうど出発して、出発するその処理パイプライン.

  • 責任デザインパターンのチェーンの古典的な例
  • 責任の連鎖の典型的なユースケースはログインプロセスです.これは、適切に一致するようにユーザー名、パスワード、captchaなどのように正常に完了する手順の特定の数が必要です.以下の伝統的な例を考えてみましょう.
  • struct Authentication {
        Authentication*     m_next{nullptr};
    
        virtual bool authenticate() = 0;
        void next_authentication(Authentication *nextAuth) { m_next = nextAuth; }
    };
    
    struct UserName : Authentication {
        string      m_name;
    
        UserName(string name) : m_name(name){}
        bool is_valid_user_name() { return true; }
        bool authenticate() {
            if(!is_valid_user_name()) {
                cout << "Invalid user name" << endl;
                return false;
            }
            else if(m_next) return m_next->authenticate();
            return true;
        }
    };
    
    struct Password : Authentication {
        string      m_password;
    
        Password(string password) : m_password(password){}
        bool is_valid_password() { return true; }
        bool authenticate() {
            if(!is_valid_password()) {
                cout << "Invalid password" << endl;
                return false;
            }
            else if(m_next) return m_next->authenticate();
            return true;
        }
    };
    
    int main() {
        Authentication *login{new UserName("John")};
        login->next_authentication(new Password("password"));
        login->authenticate();
        return EXIT_SUCCESS;
    }
    
  • 私は、これが非常に良い例ではなく、責任の連鎖の考えを伝えるのに十分であるということを知っています.上で見ることができるように、ログインはユーザ名とパスワード認証のように実行される複数の下位プロセスを必要とする一つのプロセスです.
  • だから私たちの場合login->authenticate(); 1つずつログインごとに必要な各ステップを確認する責任の連鎖を火災.
  • また、例えば、ログインプロセスでより多くのステップを追加することができますAuthentication & ログインした次の認証チェインのクラスオブジェクトポインタをUserName & Password .
  • より洗練された実装に移行する前に、私はただ責任の連鎖のこの特定の実装が全く人工的であるという事実に言及したかったです.基本的に何がここで起こっているので、質問はよく理由だけではなく、使用しているので、単一のリンクリストを構築しているstd::list またはstd::vector . それは確かに非常に有効な懸念です.しかし、以前に述べたように、これは人々がチェーンの無責任を構築するために使用された方法です.

  • 責任デザインパターンの連鎖の後押し例
  • あなたが今見に行くことは、知られている責任デザインパターンのチェーンを実装する現代的な方法ですEvent Broker . これは実際にいくつかのデザインパターンの組み合わせですCommand , Mediator & Observer .
  • #include <iostream>
    #include <string>
    using namespace std;
    #include <boost/signals2.hpp>
    //using namespace boost::signals2;
    
    struct Query {                          // Command
        int32_t     m_cnt{0};
    };
    
    struct EventObserver {                  // Observer
        boost::signals2::signal<void(Query &)>       m_handlers;
    };
    
    struct ExampleClass : EventObserver {   // Mediator
        void generate_event() { 
            cout << "Event generated" << endl;
            Query   q;
            m_handlers(q); 
            cout << endl;
        }
    };
    
    struct BaseHandler {
        ExampleClass&       m_example;
    };
    
    struct Handler_1 : BaseHandler {
        boost::signals2::connection      m_conn;
    
        Handler_1(ExampleClass &example) : BaseHandler{example}
        {
            m_conn = m_example.m_handlers.connect([&](Query &q) {
                cout << "Serving by Handler_1 : count = " << ++q.m_cnt << endl;
            });
        }
        ~Handler_1() { m_conn.disconnect(); }
    };
    
    struct Handler_2 : BaseHandler {
        boost::signals2::connection      m_conn;
    
        Handler_2(ExampleClass &example) : BaseHandler{example}
        {
            m_conn = m_example.m_handlers.connect([&](Query &q) {
                cout << "Serving by Handler_2 : count = " << ++q.m_cnt << endl;
            });
        }
        ~Handler_2() { m_conn.disconnect(); }
    };
    
    int main() {
        ExampleClass example;
        Handler_1 applyThisHandlerOn{example};
    
        example.generate_event();       // Will be served by Handler_1
    
        { 
            Handler_2 TemporaryHandler{example};
            example.generate_event();   // Will be served by Handler_1 & Handler_2
        }
    
        example.generate_event();       // Will be served by Handler_1
        return EXIT_SUCCESS;
    }
    /*
    Event generated
    Serving by Handler_1 : count = 1
    
    Event generated
    Serving by Handler_1 : count = 1
    Serving by Handler_2 : count = 2
    
    Event generated
    Serving by Handler_1 : count = 1
    */
    
  • あなたが見ることができるように、我々はExampleClass これはイベント&boost::signal2 オブザーバー.我々はQuery (i.e. Command Design Pattern ) すべてのレジスタハンドラを渡す.
  • 次に、ハンドラを配置しますlambda function コンストラクタと同じイベントでイベントを処理するには、デストラクタでde registerとなります.
  • 主に、我々は、処理するオブジェクトを宣言するだけで、ハンドラのアドホック登録を容易にしましたQuery 通過するExampleClass::generate_event() . ハンドラは自動的に、それがスコープのおかげでRAII .

  • 責任デザインパターンのチェーンの利点
  • 我々がより洗練されたアプローチを見たので、送付者とレシーバーを切り離してくださいMediator & Command Design Pattern .
  • イベントを生成しているオブジェクトがチェーン構造&コマンド/クエリを知る必要はありません.
  • オブジェクトに割り当てられた任務の柔軟性を強化します.チェーン内のメンバーを変更するか、またはその順序を変更することで、動的に追加または責任を削除できます.
  • 新しいハンドラを追加するように拡張性を高めることは非常に便利です.

  • FAQによるまとめ
    デコレータに責任デザインパターンを使用できますか?
    ——複数のデコレータを必要とするとき.
    --動的に新しい機能を追加したい.
    ——機能の設定変更が必要な場合.
    たとえば、デコレータをWalkingAnimal & BarkingAnimal of Animal , そして今、両方のランタイムで組み合わせる必要があります.そのような場合、責任の連鎖は正しい選択でしょう.
    責任デザインパターンのチェーンを使うには?
    --複数のオブジェクトがある場合、リクエストをサービスします.
    --これらのオブジェクトとその順序はリクエストタイプに基づいて実行時に決定されます.
    --リクエスト&ハンドラをしっかりバインドしない場合.