コンポーネントとインターフェースについて

エントリー


最近、システムにコンポーネントを分割する必要があるという開発者の理解不足に対処しなければならない状況にしばしば遭遇します。 この理解が存在する場合、コンポーネントの実装を隠すという誤解が明らかになります。 また、インターフェースの利便性について話す必要はありません。



以下、1つのプロセスの一部という意味でのコンポーネントを意味します。 RPCまたは他の何かを介して動作する別個のプロセスまたはサービスであるコンポーネントは、ここでは考慮されません。 大部分は次のすべてが適用されますが。



例1:

別の部門で長い間開発およびテストされたコンポーネントが必要です。 開発者に行きます。 対話が確立されます:

「これが必要です。」 プロジェクトでどのように使用しますか?

-はい、ここでこのラベルにCVSからプロジェクトを記入する必要があります。 コンパイルします。 ライブラリを入手したら、リンクする必要があります。

-はい、ありがとう。

プロジェクトを収縮させます。 コンパイルします。 多数のエラーがクロールアウトしますが、インクルードが十分ではありません。 あなたが見つけ始めます。 プロジェクトをビルドするには、CVSから多数のプロジェクトを取り出し、それらも収集する必要があることがわかります。 スタジオによって標準で構築されるものもあれば、autoconf、makeなどのタンバリンを含むものもあります。 それだけです 集まった。 リンクの問題が始まります。 1、2、3番目はリンクされていません。 サードパーティのライブラリでは不十分です。 その結果、非生産的な労力と使用済みライブラリ、サードパーティのコンポーネント、テクノロジーの理解に多くの時間を浪費しました。





例2:

「これが必要です。」 プロジェクトでどのように使用しますか?

-なるほど、これは別ではありません。 ここで、プロジェクトをダウンロードして、このクラスからこのファイルを取得する必要があります。これは無知で、これらのプロジェクトを取得して、そのように配置します...

-ああ!



例3:

「これが必要です。」 プロジェクトでどのように使用しますか?

-はい、ここにlibaがあり、ここにリンクする必要があります。

-はい、ありがとう。

ライブラリを接続しますが、リンクしません。十分な文字がありません。 ここでも、依存するコンポーネントの検索とアセンブリが開始されます。



新たな問題の発生:

1.テストされたものではなく収集されました。 一般に、コンポーネントをテストするかどうかは問題ではありません。再構築後もコンポーネントをテストする必要があります。

2.間違ったバージョンのライブラリでアセンブルされました。

3.サードパーティのコンポーネントの間違ったバージョンで組み立てられています。

4.間違ったオプションで組み立てられている。

5.収集したものが、私が望んでいたものではない

そして、必要なのは、1つのクラスの1つのメソッドだけでした...彼はより速く書けただろう。



悪はどこから来るのか


コンポーネント-オブジェクトまたは動作の完全なモデルを完全に実装するコンポーネントでもあります。 束のすべてに干渉しないでください。 通常、コンポーネントが十分に議論され、設計され、開発されている場合、そのテストのためにコンポーネントを稼働中のシステムに含める必要はありません。 コンポーネントには、自動テスト、単体テスト、負荷テストなどを実行できるインターフェイスがあります。 実際、システムを理解するのは簡単であり、システムを使用する方が便利です。



すべての悪は、インターフェースと実装を分離する必要性を理解していないことにあります。 そして、実装を隠す必要性を誤解しないでください。 まあ、私はそこで何かがどのように機能し、どのように機能するかを見たり研究したりしたくありません。 この機能を使用して、プロジェクトを完了する必要があります。



私の観点から、コンポーネントの設計を見てみましょう。



例はC ++です。



コンポーネントはどのように見えるべきか


私の謙虚な意見では、コンポーネントは、コンポーネントに実装されたインターフェイスを記述するキット内のヘッダーファイルを備えた動的ライブラリである必要があります(石が飛ぶことを期待して、dll-hellについて知っているが、適切に設計およびインストールされたシステムには存在しないことをお知らせします)。 ここでは、状況に応じて.defファイルと.libファイルを追加できますが、適切に設計されたコンポーネントでは.dll(.so)と.hで十分です。 また、動的ライブラリをランタイムライブラリに静的にリンクすることも望ましいです。 これにより、Windowsでのさまざまな再頒布可能パッケージの問題を回避できます。



ただし、静的ライブラリをコンポーネントとみなすことはできません。 静的ライブラリでは、STLやboostなどのサードパーティのコンテナに強く結び付けられた、さまざまなコンポーネントの実装のさまざまな共通部分を追加することをお勧めします。

その結果、完成したコンポーネントは、実装されテストされた機能をそのまま修正し、その使用に便利なインターフェイスを提供する必要があります。



インターフェース


例と解決策を検討してください。

静的ライブラリとインターフェイスは考慮せず、直ちに動的ライブラリとインターフェイスに渡します。

不適切なインターフェイスオプション:



#include <string> #ifdef EXPORTS #define API __declspec( dllexport ) #else #define API __declspec( dllimport ) #endif class API Component { public: const std::string& GetString() const; private: std::string m_sString; };
      
      







ここで何が間違っていますか? まあ、まず、実装は隠されていません。 クラスのメンバー、サードパーティのコンテナ、この場合はstd :: stringが表示されます。 つまり 実装の一部が見えますが、これは悪いことです。 誰かがinして、これが標準的なコンテナである場合、彼はどのような第三者ですか? コンポーネントはMicrosoft STL実装を使用でき、STLPortが必要なため、サードパーティです。 そして、そのようなコンポーネントを直接使用することはできません。 第二に、インターフェースはクロスプラットフォームではありません。 __Declspec命令は、すべてのコンパイラで使用できるわけではありません。 第三に、そのようなインターフェースを持つコンポーネントに明示的なリンクを使用することは、控えめに言っても困難です。



最初の問題を解決するには、PIMPLのイディオムと、外部コンテナーを組み込み型に置き換えることによる拒否が適しています。 2番目の問題を解決するために、defineディレクティブを拡張します。



 #ifdef WIN32 #ifdef EXPORTS #define API __declspec( dllexport ) #else #define API __declspec( dllimport ) #endif #else #define API #endif class ComponentImpl; class API Component { public: const char* GetString() const; private: ComponentImpl* m_pImpl; };
      
      







実装は非表示になり、クロスプラットフォームが追加されました。 実際、開発中のSTL実装の使用が厳密に規制されている場合、標準コンテナを放棄することはできません。 ただし、混合システムを使用すると問題が発生する場合があります。



明示的なリンクの単純さをどうするか?



これを行うには、抽象クラスとファクトリー関数を使用します。



 #ifdef WIN32 #ifdef EXPORTS #define API __declspec( dllexport ) #else #define API __declspec( dllimport ) #endif #else #define API #endif class Component; extern "C" API Container* GetComponent(); class Component { public: virtual ~Component() {} virtual const char* GetString() const = 0; };
      
      







この場合、名前が既知で変更されない単一の関数があります。 そのようなコンポーネントのダウンロードとリンクは非常に簡単です。 ライブラリをロードした後、名前で取得してGetComponent関数を呼び出すだけで十分です。 次に、Componentインターフェースの多くのメソッドすべてにアクセスできます。



さらに進むことができます。 関数がファクトリクラスのインターフェイスを返す場合、ライブラリコンポーネントをロードする手順は変更されませんが、インターフェイスを無期限に拡張する機会があります。



 #ifdef WIN32 #ifdef EXPORTS #define API __declspec( dllexport ) #else #define API __declspec( dllimport ) #endif #else #define API #endif class Factory; extern "C" API Factory* GetFactory(); class Component; class Component1; class Factory { public: virtual ~Factory() {} virtual Component* GetComponent() = 0; virtual Component1* GetComponent1() = 0; }; class Component { public: virtual ~Component() {} virtual const char* GetString() const = 0; }; class Component1 { public: virtual ~Component1() {} virtual const char* GetString() const = 0; };
      
      







曲芸飛行体として、COMイデオロギーから何かを請求することができます。これにより、既に動作しているシステムとの完全な下位互換性を維持しながら、コンポーネントの機能を無制限に拡張できます。



 #ifdef WIN32 #ifdef EXPORTS #define API __declspec( dllexport ) #else #define API __declspec( dllimport ) #endif #else #define API #endif class Factory; extern "C" API Factory* GetFactory(); class Base { public: virtual ~Base() {} virtual void QueryInterface( const char* id, void** ppInterface ) = 0; virtual void Release() = 0; }; class ConnectionPoint : public Base { public: virtual void Bind( const char* id, void* pEvents ) = 0; virtual void Unbind( const char* id, void* pEvents ) = 0; }; class Factory : public Base { }; static const char* COMPONENT_ID = "Component"; class Component : public Base { public: virtual const char* GetString() const = 0; }; static const char* COMPONENT1_ID = "Component1"; class Component1 : public Base { public: virtual const char* GetString() const = 0; };
      
      







この場合、下位互換性を維持しながら、コンポーネントにインターフェイスを追加できます。 継承によってインターフェイス自体を拡張したり、それらをファクトリとして使用したりできます。 インターフェイスにConnectionPointsを実装し、イベントハンドラーを無制限に使用する可能性を広げることができます。 この例のメモリ管理は大幅に簡素化されていますが、COMと同様に、参照カウントとスマートポインターを使用できます。



COMイデオロギーは、特に初心者にとっては理解しにくいことがよくありますが、それを使用してインターフェイスを開発すると、既に動作しているプロジェクトの整合性を損なうことなくインターフェイスを柔軟に変更し、さまざまな要件を実装できます COMアプローチは完全にクロスプラットフォームであり、開発者を混乱させるべきではありません。



もちろん、純粋なCOMアプローチを使用することは、ほとんど常に冗長です;前の例のように、抽象クラスを使用した単純な作業と組み合わせる方が良いです。



多くの場合、将来的にコンポーネントの機能を拡張する必要がないことが明らかな場合、抽象ファクトリーとファクトリー関数で十分です。



コンパイラ、サードパーティライブラリ、および作業システムとの下位互換性は不要であるという理解が明確に規定されている場合、PIMPLイディオムはインターフェイスをシンプルにするためにも最適です。



結論として


適切に設計されたインターフェイスと実装の非表示は、同じコンポーネントを再利用する際の作業に大いに役立ちます。 この場合、コンポーネントは一度組み立てられてテストされるため、テストと開発のためのリソースが大幅に節約されます。 動的にロードされるライブラリとして利用可能であり、開発者がその実装とコンパイルの複雑さを理解する必要なく、いつでもすぐに使用できます。 ライブラリをヘッダーファイルと一緒に使用して使用し、人生を楽しんでください。



PS


Javaでは、適切に設計されたコンポーネントとインターフェースが必要です。 しかし、次回については。



All Articles