NameMappedFactory

やあ子供たち元気にしてたかな。昨日おじさんはついにMacMiniを買ってしまったわけなのだけれども、Objective-Cに慣れることができるのかどうか本当に不安な気持ちでいっぱいだよ。
共通の基底を持つオブジェクトの集合があるとき、各オブジェクトに一対一で対応する名前をつけて、これらを管理したい場合がよくある。各オブジェクトには生まれた瞬間から名前を割り当て、後でその名前から該当するオブジェクトを逆引きできるようにしておきたい。そんなわかりきったよくあるソリューションを毎回書くのはいやだ、一つのテンプレート機能としてまとめてしまえばどんなに便利ですっきり抽象化できるだろうということで今回は考えてみたよ。
さて冒頭に述べたような機能を実現するには、

  1. 名前をキーとしてオブジェクトを検索できるマッピング機構
  2. オブジェクトの作成は抽象工場のみに限定
  3. 工場でのオブジェクト生成時のタイミングでマップ登録を完了

といった共通要件を実装してやれば汎用のきくものが出来そうだ。つまり任意型のキーと、任意型のオブジェクトをとる、mapとFactoryとが合体したようなジェネリックコードを作ってみよう。
そうしてできたのが以下に示すコードだ。その名もNameMappedFactory。

#pragma once



template< class T >
struct poc_tt
{
  typedef T _type;
};

template< class Name, class T >
// concept T: void T::setName( const Name& )
class pocNameMappedFactory
{
public:
  typedef std::map< Name, T* > NameMapType;
  typedef typename NameMapType::iterator NameMapTypeIter;
  typedef typename NameMapType::const_iterator NameMapTypeConstIter;

  pocNameMappedFactory( void ){}
  virtual ~pocNameMappedFactory( void ){
    // If the _name_map is to be a master list of
    // all objects, override this dtor for releasing them..
    for each( NameMapType::value_type ipair in _name_map ){
      delete ipair.second;
    }// ipair
    // or just enable the above code..
  }
  template< class Traits >
  // concept Traits: typename T<-(Traits::_type)
  typename Traits::_type* Create( const Name& name, Traits dum ){
    typedef typename Traits::_type TT;
    typename TT* result = new typename TT;
    result->setName( this->AppendPair( name, result ) );
    return result;
  }
  void Erase( T* t ){
    pocMapUtil::Erase( _name_map, t->Name(), 0 );
    return;
  }
  T* SearchByName( const Name& name, bool throw_exception=!0 ) const {
    return pocMapUtil::Search( _name_map, name, 0 );
  }
  NameMapTypeConstIter
    SearchByNameIter( const Name& name, bool throw_exception=!0 ) const {
      return pocMapUtil::SearchIter( _name_map, name, throw_exception );
  }
  template< class Func >
  void ForEachT( Func& func ){
    POC_FOREACH( _name_map, NameMapType::value_type& ipair ){
      func( static_cast< Func::argument_type >( ipair.second ) );
    }// ipair
  }

private:  
  const Name AppendPair( const Name& name, T* t ){
    pair< NameMapType::iterator, bool > pr =
      _name_map.insert( make_pair( name, t ) );
    return pr.first->first;
  }
  POC_PROP( NameMapType, _name_map, NameMap );
};

POC_PROPについてはこちらを参照してくれ。あと、pocMapUtilに関してはこちらを参照してほしい。
今回サンプルとしては以下のようなアップル社のタッチ系デバイスをクラスにしたものを用意してみた。

class AppleTouchDevice
{
protected:
  AppleTouchDevice( void ){}
public:
  void setName( const string& name ){/**/}
  virtual void Tweet( void ) const =0;
};

#define MyFactory NameMappedFactory<string,AppleTouchDevice>

class iPhone: public AppleTouchDevice
{
  friend class MyFactory;
protected:
  iPhone( void ){}
  void Tweet( void ) const { cout<<"hi im iPhone"<<endl; }
};

struct iPod: public AppleTouchDevice
{
  friend class MyFactory;
protected:
  iPod( void ){}
  void Tweet( void ) const { cout<<"hi im iPod"<<endl; }
};

struct iPad: public AppleTouchDevice
{
  friend class MyFactory;
protected:
  iPad( void ){}
  void Tweet( void ) const { cout<<"hi im iPad"<<endl; }
};

使い方はとても簡単だ。使用事例の全ソースは以下のようになる。

int main( void )
{
  //1.工場の作成
  MyFactory my_factory;

  //2.オブジェクトの作成
  my_factory.Create( "My iPod", anytype_t< iPod >() );
  my_factory.Create( "Tom's iPhone", anytype_t< iPhone >() );
  my_factory.Create( "Steve's iPhone", anytype_t< iPhone >() );
  my_factory.Create( "Your iPad", anytype_t< iPad >() );

  //3.オブジェクトの検索など、ネームマップの活用
  MyFactory::NameMapType &l = my_factory.refNameMap();
  l["My iPod"]->Tweet();
  l["Steve's iPhone"]->Tweet();
  l["Your iPad"]->Tweet();

  return 0;
}
// 実行結果↓
hi im iPod
hi im iPhone
hi im iPad

まずはキーとなるネームの型と基底オブジェクトの型を指定して、抽象工場テンプレートを実体化しよう。次にキーとなるネームとそれに関連付けたいオブジェクトの型を指定して工場のCreateメソッドを呼ぶことによって、オブジェクトの生成および、キーとの対応づけのマップ登録が完了する。
ここで、いくつか注意事項を。

  • オブジェクトの型は、setName( const Name& );というメソッドを要求し、また、
  • poc_ttに指定できる型パラメータは、オブジェクトの派生型でなくてはならないし、
  • poc_ttは、その型パラメータを、_nameとして提供することが要求される。
  • 最後に、オブジェクトがうっかり工場以外の場所で勝手にnewされたりすることのないよう、コンストラクタをprotectedとし、工場クラスをフレンドクラスに指定するのを忘れずに。

それだけ注意すればあとはマップを利用するだけだ。ネームとオブジェクトとのマッピングを保持するmap<>型は、MyFactory::NameMapTypeとして、またそのインスタンスは、my_factory.NameMap() としてアクセス可能なので、
あとはstd::mapなので検索、削除、内容のクリアなど、何でも好きなようにやればよい。
このようにNameMappedFactory自体は大した機能ではないけれども、名前で一対一管理したいオブジェクト群が独立して複数あるような場合はこのように機能の抽象化をしておくと結構楽だ。ケースに応じて改変し活用する。例えばキーの値がほぼ連続した整数値となる場合などは、mapではなくvectorを使うような特殊化版を用意して高速化を図るということも考えられる。