モダンなデザインパターン


ブリッジデザインパターンは、抽象化と実装の2つの部分にクラスを分離するために使用される構造デザインパターンです.これはクラス抽象化とその実装の間のゆるい結合を促進します.あなたは、あなたのオリジナルクラスと機能の間のブリッジとして機能するindirection、すなわちインターフェースのもう一つのレベルを加えることによって、このデカップリングを得ます.絶縁は、C++世界のブリッジデザインパターンの別の名前です.

"All problems in computer science can be solved by another level of indirection." -- David Wheeler


ところで、あなたが構造デザインパターンに関する私の他の記事をチェックしないならば、リストは以下の通りです:
  • Adapter
  • Bridge
  • Composite
  • Decorator
  • Facade
  • Flyweight
  • Proxy
  • あなたが記事のこのシリーズを通して見るコード断片は洗練されていない簡素化されます.だから、あなたはよく私のようなキーワードを使用しないを参照してください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しています.
  • /!\: This article has been originally published on my blog. If you are interested in receiving my latest articles, please sign up to my newsletter.


    意図


    To separate the interface from its implementation.

  • 言い換えれば、継承/一般化よりむしろ集約/構成を使用して、柔軟な抽象化を通して一緒に構成要素をつなぐことはすべてです.
  • このパターンはブリッジとして機能するインターフェースを含んでいます.これは、インターフェイス実装クラスから独立した具象クラスの機能を作成します.クラスの両方の種類は、互いに影響を与えることなく構造的に変更することができます.
  • 橋梁設計パターンの動機づけ

  • ブリッジ設計パターンはデカルト製品の複雑さの爆発を防ぐこの数学的な用語で怖がってはいけない、私は以下の例を簡素化している.
  • たとえば、いくつかの基本クラスがあると仮定しましょうShape それからShapeCircle or Square また、API 1またはAPI 2で描画することもできます.
  • struct DrawingAPI_1 { };
    struct DrawingAPI_2 { };
    
    struct Shape { virtual void draw() = 0; };
    
    /* 2 x 2 scenario */
    struct Circle : Shape, DrawingAPI_1 { };
    struct Circle : Shape, DrawingAPI_2 { };
    
    struct Square : Shape, DrawingAPI_1 { };
    struct Square : Shape, DrawingAPI_2 { };
    
  • あなたが2対2(2×2)シナリオを持って終わるこの方法.それで、あなたがそれを実装すると決めるならば、4つのクラスを実装しなければなりません.あなたのための1つはCircle with API_1 , Circle with API_2 など.
  • ブリッジのデザインパターンは、実際には、このエンティティの爆発を回避するパターンです.
  • したがって、上記のようなことをする代わりに、DrawingAPIインタフェースを設計します(後でAPI 1と2を引き出すために使用されます).Circle & Square .
  • ブリッジデザインパターン

  • 以下はブリッジデザインパターンの典型的な実装です.ここではとても複雑な何かを見るつもりはない.しかし本質的に橋はインタフェースまたは階層を実装から切り離すメカニズムです.
  • struct DrawingAPI {
        virtual void drawCircle() = 0;
    };
    
    struct DrawingAPI_1 : DrawingAPI {
        void drawCircle() { cout << "Drawn by API 1"<< endl; }
    };
    
    struct DrawingAPI_2 : DrawingAPI {
        void drawCircle() { cout << "Drawn by API 2"<< endl; }
    };
    
    struct Shape {
        Shape(DrawingAPI &drawingAPI) : m_drawingAPI{drawingAPI} {}
        virtual void draw() = 0;
    protected:
        DrawingAPI &m_drawingAPI;   // Now Shapes does not need to worry about drawing APIs
    };
    
    struct Circle : Shape {
        Circle(DrawingAPI &drawingAPI) : Shape{drawingAPI} {}
        void draw() { m_drawingAPI.drawCircle(); }
    };
    
    int main() {
        DrawingAPI_1 API_1;
        DrawingAPI_2 API_2;
        Circle(API_1).draw();
        Circle(API_2).draw();
        return EXIT_SUCCESS;
    }
    
  • あなたが継承と凝集に関してあまり信頼しないこの方法.むしろ、インターフェイスに依存します.
  • C ++ idiomを用いたブリッジ設計パターン:実装へのポインタ( PIMPL )

  • ブリッジデザインのパターンを話し合っている間、どのように我々はにこやかなイディオムを忘れることができます!pimpleは少し異なったブリッジデザインパターンの現れです.
  • pimpl idiomは、名前が示すようにポインタによって示された別々の実装にそれを貼り付けて、特定のクラスの実装詳細を隠すことについてです.これがどのように動作するかをお見せしましょう.
  • 人.H
    #pragma once
    #include <string>
    #include <memory>
    
    struct Person {
        /* PIMPL ------------------------------------ */
        class PersonImpl;
        unique_ptr<PersonImpl>  m_impl; // bridge - not necessarily inner class, can vary
        /* ------------------------------------------ */
        string                  m_name;
    
        Person();
        ~Person();
    
        void greet();
    private:
        // secret data members or methods are in `PersonImpl` not here
        // as we are going to expose this class to client
    };
    
    人.ビジネスロジックを隠すために、共有ライブラリ(. so/. dll)になるでしょう
    #include "Person.h"
    
    /* PIMPL Implementation ------------------------------------ */
    struct Person::PersonImpl {
        void greet(Person *p) {
            cout << "hello "<< p->name.c_str() << endl;
        }
    };
    /* --------------------------------------------------------- */
    
    Person::Person() : m_impl(new PersonImpl) { }
    Person::~Person() { delete m_impl; }
    void Person::greet() { m_impl->greet(this); }
    
  • ので、これは簡潔な形式の一種の熟語です.そして、質問はよく、なぜ、あなたは最初にこれをしたいですか.
  • セキュリティの目的では、クラスのAPIを含むクライアントにヘッダーファイルを公開する方法を質問する必要があります.さて、データメンバーとプライベートメソッドを考えてみてください.あなたが貿易秘密を持っていて、重要な情報を含むデータメンバーを持っているならば.なぜあなたも、クライアントは、クライアントの名前を知っているobject ?
  • Pimplのもう一つの利点は、それがそれのために広く批判されるようにC +のために重要であるコンパイル時間です.しかし、コンパイラがますますインクリメンタルになるにつれて、これはますます少なくなってきている.そして、彼らは最近本当に素晴らしいです.
  • 安全&高速Pimpl

  • すべてのAPIを使用する必要がありますstd::unique_ptr アクセスのたびにポインタを参照しなければならないので、いくつかの実行時オーバーヘッドを説明します.
  • 加えて我々も建設&破壊オーバーヘッドのstd::unique_ptr それは、システムコールと共に呼び出している多くの他の機能を含むヒープにメモリを作成するので.
  • また、データメンバーにアクセスしたい場合は、間接的にいくつかの間接転送をしなければなりませんPerson インPersonImpl 通過のようにthis ポインタ等.
  • 人.H
    #pragma once
    #include <string>
    #include <cstddef>
    #include <type_traits>
    
    struct Person {
        Person();
        ~Person();
        void greet();
    
    private:
        static constexpr size_t     m_size = 1024;
        using pimpl_storage_t = aligned_storage<m_size, alignment_of_v<max_align_t>>::type;
    
        string                      m_name;
        pimpl_storage_t             m_impl;
    };
    
    人.ビジネスロジックを隠すために、共有ライブラリ(. so/. dll)になるでしょう
    #include "Person.h"
    #include <iostream>
    
    struct PersonImpl {
        void greet(string &name) {
            cout << "hello "<< name << endl;
        }
    };
    
    Person::Person() {
      static_assert(sizeof(impl) >= sizeof(PersonImpl)); // Compile time safety
      new(&impl) PersonImpl;
    }
    Person::~Person() { reinterpret_cast<PersonImpl*>(&impl)->~PersonImpl(); }
    void Person::greet() { reinterpret_cast<PersonImpl*>(&impl)->greet(name);  }
    
  • したがって、配置問題の新しい演算子とあらかじめ割り当てられた整列メモリバッファでこの問題に対処しましょう.reinterpret_cast ちょうど他のindirectionでないように、ちょうどコンパイル時代用です.
  • ブリッジデザインパターンの利点

  • ブリッジデザインパターンは、抽象化(すなわちインタフェース)と実装を独立して開発する柔軟性を提供します.そして、クライアント/APIユーザコードは、実装部分について心配することなく抽象化部分だけにアクセスすることができます.
  • 保存するOpen-Closed Principle , 言い換えれば、クライアント/APIのユーザコードとして拡張性を向上させる抽象化に依存しますので、実装はいつでも変更または拡張できます.
  • ブリッジデザインパターンを使用してPIMPL . として我々は、クライアントからの実装の詳細を隠すことができますPIMPL 上記の熟語の例.
  • ブリッジデザインパターンは、古い継承のアプリケーションです.それはあなたが互いに直交する方法で異なるクラスをサブクラスでなければならないときに便利です(2×2問題は以前に議論する).
  • 抽象化とその実装の間のコンパイル時の結合は避けるべきです.実装が実行時に選択できるようにします.
  • FAQによるまとめ


    橋梁設計パターンの実用事例は?
    どんなインターネット・ブラウザのプラグインも、このパターンを直接活用します.そして、ブラウザーが抽象化と実装だけが特定の種類のプラグインによって異なると指定します.
    いつブリッジデザインパターンを使用するには?
    --実装やそのバリエーションがわからないときには、まだ開発に進んでいきたいです.
    すなわち、行動置換問題の場合、デカルト積の複雑な爆発.
    アダプタとブリッジのデザインパターンの違いは何ですか?
    アダプターは一般的に既存のアプリケーションで使用されます.
    ブリッジは通常、前面に設計されていて、アプリケーションのパーツを独立に開発させています.
    戦略とブリッジデザインパターンの違いは何ですか?
    --Strategy マルチビットドライバーのような単一次元の問題です.
    --ブリッジは通信タイプやデバイスのような多次元問題です.