[ C++で開発 ]
C++では、ヘッダファイルにクラスの実装詳細(非公開のメンバー他)を記述します。このヘッダファイルはクラスの利用者がインクルードして使用します。そのため、クラスの利用法(インタフェース)は全く変更がなくても、クラスを利用しているソースまでコンパイルし直す必要があります。また、クラスが内部的に利用しているライブラリがあったとしても、そのライブラリのヘッダファイルが間接的にクラスの利用者にインクルードされるため、コンパイル時に多大な結合が発生します。
アプリケーションを開発するときに、利用したライブラリが内部で利用している別なライブラリの環境までちゃんと用意しないとえらいことになります。どこかに変更があってもえらいことになります。まったくC++はどこかにちょっとでも変更があれば、すべてのソースを再ビルドせよ、ということに他なりません。
これでは、「C++で大きなプログラムを作ることはできないじゃないか」と声を大にして言いたくなります。
C++では、ヘッダーファイルはprivateであっても利用者と提供者との間の契約です。
この問題を解決するための方法に絶縁があります。以下の書籍で紹介している方法です。
「大規模C++ソフトウェアデザイン」 ジョン・ラコス
著, 滝沢 徹+牧野 祐子 訳、ピアソン、1999.6
http://www.pearsoned.co.jp/washo/prog/wa_pro15-j.html
絶縁とは、不要なコンパイル時結合を回避することです。絶縁には以下の方法があります。
「4.完全絶縁具体クラス」と同様の解決方法については、他にもいろいろな書籍で取り上げられています。
#includeでヘッダファイルを取り込む記述をクラスを前方宣言とすることで依存性を低減します。継承の場合、必ずヘッダファイルを取り込まなくてはなりません。継承をコンポジションにすることによってヘッダファイルを取り込む必要がなくなります。
データメンバー(実体)は、その型を定義したヘッダーファイルを#includeで取り込まなくてはなりません。
そこで、データメンバーを実体ではなくポインタに変更することで、#includeでヘッダーファイルを取り込まずにデータメンバーの型を前方宣言(forward declaration)とすることができます。これによって#includeの依存性を低減します。
しかしながら前方宣言はその型がclassとして定義されているのかtypedefなのか、templateなのかを調べて、実際の定義に合わせて宣言を書く必要があります。特にtemplateのtypedefとなるとやっかいです。そのため、前方宣言で逃げれないケースも多々発生します。
以下の条件を持つクラス
Java言語におけるinterfaceと似た使用方法です。
プロトコルクラスを定義します。
#pragma once #include <string> #include <boost/shared_ptr.hpp> using std::string; using boost::shared_ptr; namespace character { class Character { public: virtual ~Character(); virtual const string getName() const = 0; virtual void setName(const string& aName) = 0; static shared_ptr<Character> create(const std::string& aName); }; } |
プロトコルクラスの実装クラスを定義します。
#pragma once #include "Character.h" #include <string> using std::string; namespace character { class ConcreteCharacter : public Character { public: ConcreteCharacter(const std::string& aName) : name(aName); virtual ~ConcreteCharacter(); virtual const string getName() const; virtual void setName(const string& aName); private: string name; }; } |
プロトコルクラスの実装クラスの実装を記述します。
#pragma once #include "ConcreteCharacter.h" #include <string> #include <boost/shared_ptr.hpp> using std::string; using boost::shared_ptr; namespace character { ConcreteCharacter::ConcreteCharacter(const std::string& aName) : name(aName) {} ConcreteCharacter::~ConcreteCharacter() {} virtual const string ConcreteCharacter::getName() const { return name; } virtual void ConcreteCharacter::setName(const string& aName) { name = aName; } shared_ptr<Character> Character::create(const string& aName) { return shared_ptr<Character>(new ConcreteCharacter(aName)); } } |
コンポジションのテクニックを使う。
Pimpl、ハンドルクラス、エンベローブクラス、チェシャ猫クラス、とも言われる
ファサードのテクニックの応用