ここではテキストやビットマップ、音声など各種のデータを統一的なインターフェースで操作できるクラスライブラリを開発してみます。イメージとしては、それぞれのデータがオブジェクトとしてコンテナに格納され、検索や保存、ネット上への送信などの各種操作をインターフェースを通して行えるような感じでしょうか。ビットマップや文章を編集する日常的なアプリケーションはもちろん、データベースの開発にも利用しやすいクラスライブラリにまとめられれば、と思っています。
今回は、全体のクラス階層の大枠を考えながら、最も基本的なデータであるバッファを扱うクラスを実装してみることにします。
クラス階層は、まずデータを扱うためのいくつかのインターフェースを定義し、実際のデータを扱うクラスはそれらのインターフェースの中から必要なものを実装する形にします。ただし、すべてのオブジェクトはオブジェクトの基本インターフェースである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を通したインターフェースの取得・インターフェースによる操作という基本的な部分はうまく動くようですね。次回からは、さまざまなデータ形式のクラスやデータ操作用の各種インターフェースを作っていくことにしましょう。