Entry

プログラミングメモ - テンプレート版 Builder パターンは Policy パターンの特殊な場合という話

2010年03月28日

超便利なパターンなのに,なぜか GoF の解説で軽く扱われてしまう Builder パターンの話。ネットにある Builder パターンの解説にあまりいいお題がないのは,多分適用場面を具体的に想像できていないからじゃないかと思ったりもする。

Builder パターンを使う動機は,オブジェクトを作るにあたって,その「作り方」を分離したい,というものだったりします。Builder パターンの解説には,「複雑なオブジェクト」とかいった限定を入れているものがあるけれども,別に複雑じゃなくてもいい。

この点,GoF 本では RTF パーサの例を挙げているけれども,もう少し簡単な例でも有用だったりします。例えば,画像クラスを作る場合。

巷には,たくさんの「画像クラス」があります。画像処理ライブラリの数だけ画像クラスがあるといってもいいくらい。それは,1ピクセルのデータの持ち方ひとつをとっても違います。あるところでは,単純に unsigned char の配列でバッファを持っているかもしれないし,unsigned long 中 RGB 値を各ビットに振っている持ち方をしているかもしれない。少し凝ったところでは,構造体に RGB のメンバを用意しているところもあります。また,処理速度を考慮した外部ライブラリでは,もう少し特殊な持ち方をしているところもある。例えば,IPP(Intel Performance Primitive; Intel の演算ライブラリ)では,SSE を利用することから,32バイト境界にアラインされた形でメモリを確保していたりします。

ともあれ,ピクセルデータの持ち方がいくつあったとしても,自分のところでそれらの画像を扱う場合は,ひとつクラスの中でそれらをラップして扱いたくなるわけです。そうじゃないと,いちいち関数を呼び分けたり,別々にクラスを作って使い分けなくちゃいけなくなる。クラスの再利用も進みません。

とゆことで,こゆクラスを作ります。

////////////////////////////////////////////////////////////
// 画像クラス - Director に相当
class Image {
public:
  explicit Image(Allocator* allocator);
public:
  void init(int width, int height) {
    allocator_.alloc(width, height);
  }
private:
  Allocator* allocator_;
};

////////////////////////////////////////////////////////////
// メモリ確保クラスの基底クラス - Builder に相当
class Allocator {
protected:
  Allocator();
public:
  virtual void alloc(int width, int height) throw (std::bad_alloc) {
    // 何もしない
  }
};

////////////////////////////////////////////////////////////
// メモリ確保クラスの派生クラス - ConcreteBuilder に相当
class PlainAllocator : public Allocator {
public:
  virtual void alloc(int width, int height) throw (std::bad_alloc) {
    // unsigned char の配列を確保する
    holder_ = new unsigned char[width * height];
  }
private:
  unsigned char* holder_;
};

////////////////////////////////////////////////////////////
// メモリ確保クラスの派生クラス - ConcreteBuilder に相当
class IPPAllocator : public Allocator {
public:
  virtual void alloc(std::size_t size) throw (std::bad_alloc) {
    // IPP 用のメモリ確保関数を呼んで確保する
    holder_ = ippiMalloc(width, height, &steps_);
  }
private:
  Ipp8u* holder_;
  int steps_;
};

ちょっと実装として不適切なところがあるので,このままどこかのプログラムで使用することは避けてください(Allocator が Image の存続中に delete された場合について考慮していないとか)。雰囲気だけ。

ともかくも,上のように,単純にメモリの確保の仕方が違うだけでも,Builder パターンは有効です。注目してもらいたいのは,Image クラスが具体的にメモリを確保する関数を知らなくてもいいところ。Image クラスは,Allocator クラスのインターフェイス alloc を通じてメモリを確保するだけです。実際にメモリを確保しているのは,Allocator クラスの派生クラスである,PlainAllocator クラスと IPPAllocator クラスになる。使うときは,こう使う。

int
main(int argc, char* argv[]) {
  IPPAllocator allocator;  // IPP を使う
  Image image(&allocator); // アロケータをセット
  // 240x360 ピクセルのメモリを確保
  image.init(240, 360);
  return 0;
}

こうしておくことで,画像クラスは,クラスの本体から,メモリ確保の仕方を分離しておくことができます。新しいデータ構造を使うことになった場合も,Allocator クラスから派生させた新しいメモリ確保クラスを実装して,アロケータをこのクラスに切り替えるだけでいい。Image クラスを修正する必要がなくなります。

Builder パターンは,よく現場監督と工員の「比喩」が使われます。おそらく,Director と Builder の直訳から類推した比喩なんだろうけれども,あの比喩には,少なくとも設計上の意味はないと思った方がいい。

ところで,ここからが本題なんですけれど,Builder パターンを C++ のテンプレートで実装すると Policy パターンと呼ばれるパターンで表現できたりします。Allocator と聞いてピンときた方もいたかもしれませんけど(STL のコンテナでは std::allocator<T> で Policy パターンを構成している)。

やり方はいくつかあるんですけれど,あたしが好きなパラメタライズドクラスのパターンを使うと,次のような感じになります(Allocator<T> をどう構成するかは考えてみてください)。

template<typename T, typename AllocatorT = Allocator<T> >
class Image : public AllocatorT {
public:
  void init(int width, int height) {
    AllocatorT::alloc(width, height);  // 基底クラスにしたアロケータ
  }
};

こっちの方がシンプルで柔軟性があるし,仮想関数にしない分だけ幾分か速いはずです。

GoF パターンと C++ のテンプレートにまつわるパターンは,割と重複しているところがある感じもします。テンプレートで Strategy パターンを実装している例は,よく見るんですけどね。ここら辺について,あたしゃまだ整理できていないところがあるので,もう少しつき合わせて見てみようかと思っていたりします。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN