C++ で ObservableCollection を実装する

ObservableCollection(T) クラス (System.Collections.ObjectModel)
http://msdn.microsoft.com/ja-jp/library/ms668604.aspx
Skeleton of GOF’s Design Pattern
http://www002.upp.so-net.ne.jp/ys_oota/mdp/Observer/index.htm

2つのViewの変更が、同時に行われるときのModelの挙動を考察 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3575

で、__event/__hook で実装しようと思っていたのですが、基本に帰って Observer パターンを調べてみると、ああ、これでいいじゃんということにしました。車輪の再発明ってやつです。

  • View と Data/Model を独立させよう
  • Model の変更を複数の View に伝える

という点も従来の Observer パターンで ok です。
このあたり、notify で渡す時に、notify( Model * ) のように Model の型を渡してしまうと Model の型に縛られる(当たり前)ってことになるのですが、ここは object 型/void *で十分な訳です。受ける側で、(Model*)object としてキャストをすれば良いわけで…なるほど。

複数の view への通知に対しては、addObjserver/revemoOPbserverを実装して通知先の Observable をコレクションしておきます。元々の Observer パターンがこうなので、私の見落としですね。

で、GridView と データコレクションとの連携、いわゆるコレクションのデータバインドに関しては、ObservableCollection が良く使われるので、これを C++ で実装してみます。

// Doc-View に直す暫定 Observable パターン
#include <string>
#include <iostream>
#include <list>
#include <algorithm>

using namespace std;

class IObserver {
public:
	// Action
	enum Action {
		Add,
		Remove,
		Replace,
		Move,
		Reset,
	};
	virtual void CollectionChanged( void *ob, Action mode ) {}
	virtual void PropertyChanged( void *ob, const char *propName ) {}
};

class IObservable {
protected:
	std::list<IObserver*> _lst;
public:
	void addObserver( IObserver *ob ) {
		_lst.push_back( ob );
	}
	void removeObserver( IObserver *ob ) {
		_lst.remove( ob );
	}
	void notify( const char *propName ) {
		for ( auto it = _lst.begin(); it != _lst.end(); ++it ) {
			(*it)->PropertyChanged( this, propName );
		}
	}
};

template <class T> class ObservableCollection : public std::list<T> {
protected:
	std::list<IObserver*> _lst;
public:
	void addObserver( IObserver *ob ) {
		_lst.push_back( ob );
	}
	void removeObserver( IObserver *ob ) {
		_lst.remove( ob );
	}
	void notify( T item, IObserver::Action mode ) {
		for ( auto it = _lst.begin(); it != _lst.end(); ++it ) {
			(*it)->CollectionChanged( item, mode );
		}
	}
public:
	// override
	void push_back( T item ) {
		std::list<T>::push_back( item );
		notify( item, IObserver::Add );
	}
	void remove( T item ) {
		std::list<T>::remove( item );
		notify( item, IObserver::Remove );
	}
	void removeAt( int index ) {
		for ( auto it=begin(); it!=end(); ++it ) {
			if ( index-- == 0 ) {
				remove( *it );
			}
		}
	}
	void insert( std::list<T>::iterator it, T item ) {
		std::list<T>::insert( it, item );
		notify( item, IObserver::Add );
	}
		
	void insertAt( int index, T item ) {
		for ( auto it=begin(); it!=end(); ++it ) {
			if ( index-- == 0 ) {
				insert( it, item );
			}
		}
	}
	void clear() {
		std::list<T>::clear();
		notify( item, IObserver::Reset );
	}
};

#define PROPERTY( _t, _m ) \
public:	_t m_##_m;	\
public:				\
	__declspec(property(get=get##_m,put=set##_m)) _t _m;	\
	_t get##_m() { return m_##_m; }	\
	void set##_m( _t v ) {			\
		if ( m_##_m != v ) {		\
			m_##_m = v;				\
			notify( #_m );			\
		}							\
	}

class Data : public IObservable {
public:
	PROPERTY( string, Name );
	PROPERTY( int, Age );
public:
	Data( string name, int age ) {
		this->m_Name = name;
		this->m_Age = age;
	}
};
class View : public IObserver
{
public:	
	virtual void CollectionChanged( void *sender, IObserver::Action mode ) 
	{
		Data *data = (Data*)sender;
		cout << "name: " << data->Name << " " << mode  << endl;
	}
	virtual void PropertyChanged( void *sender, const char *propName ) 
	{
		Data *data = (Data*)sender;
		cout << "name: " << data->Name << " " << propName  << endl;
	}
};

int main( void )
{
	ObservableCollection<Data*> lst;
	
	lst.push_back( new Data( "masuda", 20 ));
	lst.push_back( new Data( "tomoaki", 30 ));
	lst.push_back( new Data( "alice", 10 ));
	
	View view;
	// append handler
	lst.addObserver( &view );
	for_each( lst.begin(), lst.end(), 
		[&view](Data *v){ v->addObserver( &view );});
	
	Data *d = new Data( "nanashi", 20 );
	d->addObserver( &view );
	// append item
	lst.push_back(d);
	// change property
	d->Name = "NANASHI";
	
	return 0;
}

C#/VB の ObservableCollection と合わせるために、CollectionChanged と PropertyChanged というイベントが発生します。通知元の Model/Data では IObservable を継承して、通知先の View では IObserver を継承します。__event/__hook を使うと、直接メンバ関数にマッピングができるので継承自体がいらないのですが、名称の統一性を考えると明示的に継承したほうがよさそうです。

Collection にした場合、それぞれの要素の IObserver のコレクション _lst が無駄に思えるので、もう少し改修は必要でしょうねぇ。まぁ、昨今のメモリ事情ならばこのままでも良いかと。

IObserver/IObservable というインターフェースのような名前にしていますが、実装が入っています。純粋関数にしても良いのですが、継承するけど通知されたくない(?)という中途半端な状態でも ok なので、これでよいかと。

カテゴリー: C++ パーマリンク