読者です 読者をやめる 読者になる 読者になる

Life, Education, Death

プログラミング以外でも思ったことをつらつらと書きたい

templateでObserverパターン

20091109 修正

ListenerCollection::Remove()内のstd::vector::erase()にconst_iteratorを渡していたのをiteratorに修正。

vector::erase - C++ Referenceをみる限り、const_iteratorで通らなそう。書いたのが結構前だけど、(VC9で)通してから貼り付けたつもりだったのに。



C++ テンプレートテクニックを読んでいたせいで、テンプレート厨っていう病気にかかっている最中です。
ちょうどObserverを使う機会があり、実装してみたってわけで。

実装編

#include <vector>
#include <iostream>
#include <string>
#include <algorithm>

/**
	更新の通知を受けるリスナーの基底クラス
*/
class UpdateListener
{
public:
	// コンストラクタ
	UpdateListener(){}
	// デストラクタ
	virtual ~UpdateListener(){}

	// 更新を受け取る
	virtual void OnUpdate(const std::string& message) = 0;
};

class HogeAUpdateListener : public UpdateListener
{
public:
	// 更新を受け取る
	void OnUpdate(const std::string& message){
		std::cout << "HogeAUpdateListener " << message << std::endl;
	}
};

class HogeBUpdateListener : public UpdateListener
{
public:
	// 更新を受け取る
	void OnUpdate(const std::string& message){
		std::cout << "HogeBUpdateListener " << message << std::endl;
	}
};

/**
	リスナーを管理するクラス
 */
template< class ClassType, typename ParamType, typename void (ClassType::*FuncType)( const ParamType& ) >
class ListenerCollection
{
private:
	typedef std::vector<ClassType*> Container;

public:
	// リスナーの追加
	void Add( ClassType *pListener )
	{
		m_list.push_back( pListener );
	}
	// リスナーの削除
	void Remove( const ClassType* pListener )
	{
		Container::iterator endIte = std::remove( m_list.begin(), m_list.end(), pListener );
		m_list.erase( endIte, m_list.end() );

	}
	// 全てのリスナーに通知する
	void NotifyAll( const ParamType& message )
	{
		for( Container::iterator ite = m_list.begin(); ite!=m_list.end(); ++ite ){
			((*ite)->*FuncType)( message );
		}
	}
private:
	Container m_list;
};

int main(int argc, char* argv[])
{
	ListenerCollection<UpdateListener, std::string, &UpdateListener::OnUpdate > updateListenerCollection;
	HogeAUpdateListener listener1;
	HogeBUpdateListener listener2;

	updateListenerCollection.Add( &listener1 );
	updateListenerCollection.Add( &listener2 );
	
	updateListenerCollection.NotifyAll( "hogehoge" );

	updateListenerCollection.Remove( &listener1 );

	updateListenerCollection.NotifyAll( "hogehoge2" );

	return 0;
}

結果はこんな感じになる。

HogeAUpdateListener hogehoge
HogeBUpdateListener hogehoge
HogeBUpdateListener hogehoge2

考察編

今回の実装は、引数を一つ取るリスナーをキックできるクラスでテンプレート引数は三つ。
ClassTypeはリスナークラス。ParamTypeはそのクラスが取る引数の型。最後は関数ポインタ。
リスナークラスがParamTypeに当たるのもの提供してくれれば、もう一つテンプレート引数を減らせる。


目標としては、

  • リスナーのキックする関数名を固定にしたくない
  • テンプレート引数は少なく
  • ある程度現実的な実装

だったので、だいたいクリアできたかな?まぁ動くものが出来てよかった。


このクラスがどう使われるかによって、内部のコンテナは変更されるべきだと思う。それとリスナーの削除について少し考えてみた。

削除のプランを三つ考えた。

  • コンテナをvectorにして追加と参照を早くする
  • コンテナをlistにして途中の要素の削除を早くする
  • コンテナをvectorにしてオブジェクトを削除したらNullObjectを入れて、すぐに削除しない。


メモリの使い方とどの要素(追加、削除、参照)にウェイトを置くかで選択か変わるんだろうな。
今回は参照が多いと想定してる。


SingletonのテンプレートはGamePrograming Gemsで見たんだけども、他にはどんなパターンがテンプレート化できるんだろう(ってかする価値があるんだろう)

Factoryとかどうしようもないだろうし、他にもかけそうならトライしてみよう。

最後に

そろそろModern C++を読む体力もついたんじゃないだろうかと過信してる。今週は頑張って読もう。