ここではテキストやビットマップ、音声など各種のデータを統一的なインターフェースで操作できるクラスライブラリを開発してみます。イメージとしては、それぞれのデータがオブジェクトとしてコンテナに格納され、検索や保存、ネット上への送信などの各種操作をインターフェースを通して行えるような感じでしょうか。ビットマップや文章を編集する日常的なアプリケーションはもちろん、データベースの開発にも利用しやすいクラスライブラリにまとめられれば、と思っています。
今回は、全体のクラス階層の大枠を考えながら、最も基本的なデータであるバッファを扱うクラスを実装してみることにします。
クラス階層は、まずデータを扱うためのいくつかのインターフェースを定義し、実際のデータを扱うクラスはそれらのインターフェースの中から必要なものを実装する形にします。ただし、すべてのオブジェクトはオブジェクトの基本インターフェースであるD2IObjectクラスを継承するものとし、このインターフェースを通してオブジェクトに関する情報、さらにオブジェクトが持つインターフェースを取得できるようにしましょう。
class D2IObject { public: virtual UI32 getObjectType() const=0; virtual UI8 getVersion() const=0; virtual UI8 getDataType() const=0; virtual UI16 getEncoding() const=0; virtual void * getInterface(UI32) const=0; virtual CSTR getCSTR()=0; virtual ~D2IObject() {} };
今回は、型名を以下のようにtypedefしています。
typedef BYTE UI8; typedef WORD UI16; typedef DWORD UI32; typedef char I8; typedef short I16; typedef int I32; typedef LPBYTE BUF; typedef char CHR; typedef char * CSTR;
これらの関数のうち
virtual UI32 getObjectType() const=0; virtual UI8 getVersion() const=0; virtual UI8 getDataType() const=0; virtual UI16 getEncoding() const=0;
は、オブジェクト自体やオブジェクトが保持するデータの形式に関する情報を取得するものです。また、getInterface()は、インターフェースを指定するとオブジェクトがそのインターフェースに対応している場合はそのインターフェースのポインタを返します。getCSTR()は、オブジェクトの文字列表現を返します。
すべてのオブジェクトはこのD2IObjectのサブクラスなので、これらの関数はすべてのオブジェクトで使用できます。各種のデータを格納したオブジェクトを統一的に扱う場合は、D2IObject型ポインタにオブジェクトを格納しgetDataType()などでデータの種類を判別しながら、必要に応じてgetInterface()で行いたい操作のためのインターフェースを取得する形になるでしょう。
今回は、バッファを扱うためのデータクラスを作ってみるので、以下のようなバッファを読み書きするインターフェースを定義してみました。関数の機能は、大体名前どおりです。
class D2IBufferReader { public: virtual BUF copyBuffer() const=0; virtual I32 getBufferSize() const=0; virtual UI8 readBufferByte(I32) const=0; virtual bool equalBuffer(const BUF,I32) const=0; virtual bool equalBuffer(const D2IBufferReader *) const=0; ~D2IBufferReader() {} }; class D2IBufferWriter { public: virtual BUF getBuffer() const=0; virtual bool resizeBuffer(I32,bool bArg=true)=0; virtual bool assignBuffer(const BUF,I32)=0; virtual bool assignBuffer(const D2IBufferReader *)=0; virtual bool writeBuffer(const BUF,I32,I32)=0; virtual bool writeBufferByte(UI8,I32)=0; virtual bool fillBuffer(UI8,I32,I32)=0; virtual bool appendBuffer(const BUF,I32)=0; virtual bool appendBuffer(const D2IBufferReader *)=0; virtual bool appendBufferByte(UI8)=0; ~D2IBufferWriter() {} };
次に、「バッファに格納されたデータを持つオブジェクト」を扱うためのインターフェースD2IBufferedObjectを、D2IObject/D2IBufferReader/D2IBufferWriterの各インターフェース(抽象クラス)を多重継承する形で定義しておきます。
class D2IBufferedObject:public D2IObject,public D2IBufferReader,public D2IBufferWriter { public: virtual ~D2IBufferedObject() {} };
以上で、バッファ形式のデータオブジェクトを操作(オブジェクト全般の操作とバッファの読み書き)するD2IBufferedObjectインターフェースができました。今回のバッファオブジェクトクラスD2CBufferは、このD2IBufferedObjectインターフェースを以下のような形で実装することにしましょう。
class D2CBuffer:public D2IBufferedObject { public: D2CBuffer(); D2CBuffer(const BUF,I32); D2CBuffer(const D2IBufferReader *); virtual bool clear(); virtual ~D2CBuffer(); // D2IObject virtual UI32 getObjectType() const; virtual UI8 getVersion() const; virtual UI8 getDataType() const; virtual UI16 getEncoding() const; virtual void * getInterface(UI32) const; virtual CSTR getCSTR(); // D2IBufferReader virtual BUF copyBuffer() const; virtual I32 getBufferSize() const; virtual UI8 readBufferByte(I32) const; virtual bool equalBuffer(const BUF,I32) const; virtual bool equalBuffer(const D2IBufferReader *) const; // D2IBufferWriter virtual BUF getBuffer() const; virtual bool resizeBuffer(I32,bool bArg=true); virtual bool assignBuffer(const BUF,I32); virtual bool assignBuffer(const D2IBufferReader *); virtual bool writeBuffer(const BUF,I32,I32); virtual bool writeBufferByte(UI8,I32); virtual bool fillBuffer(UI8,I32,I32); virtual bool appendBuffer(const BUF,I32); virtual bool appendBuffer(const D2IBufferReader *); virtual bool appendBufferByte(UI8); protected: BUF m_bfBuffer; CSTR m_csStr; I32 m_i32BufferSize; virtual bool setBuffer(const BUF); virtual bool setBufferSize(I32); virtual bool deleteBuffer(); };
バッファの操作は、protectedポインタm_bfBufferにバッファを確保して行います。m_csStrはgetCSTR()で返すための文字列表現ですが、今回は単純にバッファをコピーして無条件に末尾に0を付加して返すだけにしました。まあ、今回は実装というよりは設計のテストですから、実際のバッファ周りの実装はあまり気にしないでください(^^;。
指定されたインターフェースを返すgetInterface()は、以下のように指定されたインターフェースを持っていれば自分自身のポインタをそのインターフェースにキャストして返します。
void * D2CBuffer::getInterface(UI32 u32Arg) const { switch (u32Arg) { case ID_D2IObject: return (D2IObject *)this; case ID_D2IBufferReader: return (D2IBufferReader *)this; case ID_D2IBufferWriter: return (D2IBufferWriter *)this; case ID_D2IBufferedObject: return (D2IBufferedObject *)this; default: return NULL; } };
今回のプログラムは、D2CBufferとインターフェースのテストプログラムです。最初にD2CBufferのインスタンスbTestとbTest2を作り、適当な文字列を入れておきます。続いて
D2IObject *poTest=new D2CBuffer(&bTest);
としてpoTestを生成し、D2IObject型ポインタに格納しておきます。次に、D2IObjectインターフェースを通してD2IBufferWriterインターフェースを取得し、そのD2IBufferWriterインターフェースでbTestのバッファに追記してみました。
D2IBufferWriter *pbwTest=(D2IBufferWriter *)poTest->getInterface(ID_D2IBufferWriter); pbwTest->appendBufferByte('A');
結果を見ると、D2IObjectを通したインターフェースの取得・インターフェースによる操作という基本的な部分はうまく動くようですね。次回からは、さまざまなデータ形式のクラスやデータ操作用の各種インターフェースを作っていくことにしましょう。