拡張可能なインターフェイス

今回は拡張可能なインターフェイスというものについて考えてみましょう

今 Fig.1 のような抽象基底クラスがあるとします
Fig.1

struct ISamplePlugin : public ISampleUnknown {
	virtual void*	DynamicCast( int	classid ) = 0;
	virtual unsigned long	AddRef() = 0;
	virtual unsigned long	Release() = 0;
	...
	virtual bool	Save( ISampleSave*	pSave ) = 0;
	virtual bool	Load( ISampleLoad*	pLoad ) = 0;
	...
};

このような抽象基底クラスを使ったオブジェクトを実装しようとするユーザーは、自分の実装するオブジェクトにたとえ Save や Load が必要なかろうとも必ず Save も Load も実装する必要があります
Fig.2

class CSamplePlugin : public ISamplePlugin {
	...
public:
	...
	virtual bool	Save( ISampleSave*	pSave );
	virtual bool	Load( ISampleLoad*	pLoad );
	...
};

bool	CSamplePlugin::Save( ISampleSave*	pSave ){
	return	true;
}
bool	CSamplePlugin::Load( ISampleLoad*	pLoad ){
	return	true;
}

この例では無駄なメソッドは2つだけですが、それが10も20もあるようだったらどうでしょうか
また Fig.1 の抽象基底クラスに新しく機能を追加しようとするとして Fig.3 のようにした場合はどうでしょうか
Fig.3

struct ISamplePlugin : public ISampleUnknown {
	virtual void*	DynamicCast( int	classid ) = 0;
	virtual unsigned long	AddRef() = 0;
	virtual unsigned long	Release() = 0;
	...
	virtual bool	Save( ISampleSave*	pSave ) = 0;
	virtual bool	Load( ISampleLoad*	pLoad ) = 0;
	...
	// 追加機能
	virtual int foo() = 0;
	virtual int bar() = 0;
	...
};

この追加機能が既存のクラスを実装している人は気にしなくてもいいような機能だったとしても、既存のコードには空の実装を追加することが必要になってしまいます
必要も無いメソッドの実装をいくつも書かなければならないことはユーザーの負担になりますし、既存のコードから気にしなくていい追加機能なら既存のコードは修正しないで済む方がいいですよね

このようなときには拡張可能なインターフェイスが役に立ちます
拡張可能なインターフェイスはマルチインターフェイスと RTTI を用いると実現できます
マルチインターフェイスは機能をそれぞれ適切な抽象基底クラスに分けることから始まります
Fig.4

#define classid_ISampleUnknown	0
#define classid_ISampleFooBar	3
#define classid_ISamplePlugin	4
#define classid_ISampleSaveLoadCallback 5

struct ISamplePlugin : public ISampleUnknown {
	virtual void*	DynamicCast( int	classid ) = 0;
	virtual unsigned long	AddRef() = 0;
	virtual unsigned long	Release() = 0;
	...
};
struct ISampleSaveLoadCallback : public ISampleUnknown {
	virtual void*	DynamicCast( int	classid ) = 0;
	virtual unsigned long	AddRef() = 0;
	virtual unsigned long	Release() = 0;
	...
	virtual bool	Save( ISampleSave*	pSave ) = 0;
	virtual bool	Load( ISampleLoad*	pLoad ) = 0;
	...
};
struct ISampleFooBar : public ISampleUnknown {
	virtual void*	DynamicCast( int	classid ) = 0;
	virtual unsigned long	AddRef() = 0;
	virtual unsigned long	Release() = 0;
	...
	virtual int foo() = 0;
	virtual int bar() = 0;
	...
};

こうして機能ごとに抽象基底クラスが分けてあればユーザーは自分の必要な機能の抽象基底クラスだけを実装すればよくなります
例えば Save/Load も foo/bar も必要の無いオブジェクトでは Fig.5 のように
例えば Save/Load も foo/bar も必要なオブジェクトでは Fig.6 のように
Fig.5

// Save/Load も foo/bar も必要の無いオブジェクトの実装
class CSamplePlugin : public ISamplePlugin {
	long	m_cRef;
	...
public:
	virtual void*	DynamicCast( int	classid );
	virtual unsigned long	AddRef();
	virtual unsigned long	Release();
	...
	CSamplePlugin();
	virtual ~CSamplePlugin();
};
CSamplePlugin::CSamplePlugin():
m_cRef(1)
{
}
CSamplePlugin::~CSamplePlugin(){
}
void*	CSamplePlugin::DynamicCast( int	classid ){
	void*	pvObject = NULL;
	if( classid == classid_ISampleUnknown ){
		pvObject = ( ISampleUnknown* )this;
	}
	else if( classid == classid_ISamplePlugin ){
		pvObject = ( ISamplePlugin* )this;
	}
	return	pvObject;
}
unsigned long	CSamplePlugin::AddRef(){
	return	++m_cRef;
}
unsigned long	CSamplePlugin::Release(){
	if( --m_cRef == 0 ){
		delete this;
		return	0;
	}
	return	m_cRef;
}

Fig.6

// Save/Load も foo/bar も必要なオブジェクトの実装
class CSamplePlugin : public ISamplePlugin, public ISampleSaveLoadCallback, public ISampleFooBar {
	long	m_cRef;
	...
	int m_foo;
	int m_bar;
	...
public:
	virtual void*	DynamicCast( int	classid );
	virtual unsigned long	AddRef();
	virtual unsigned long	Release();
	...
	virtual bool	Save( ISampleSave*	pSave );
	virtual bool	Load( ISampleLoad*	pLoad );
	...
	virtual int	foo();
	virtual int	bar();
	...
	CSamplePlugin( int foo, int	bar );
	virtual ~CSamplePlugin();
};
CSamplePlugin::CSamplePlugin( int foo, int	bar ):
m_cRef(1),
m_foo(foo),
m_bar(bar)
{
}
CSamplePlugin::~CSamplePlugin(){
}
void*	CSamplePlugin::DynamicCast( int	classid ){
	void*	pvObject = NULL;
	if( classid == classid_ISampleUnknown ){
		pvObject = ( ISampleUnknown* )( ISamplePlugin* )this;
	}
	else if( classid == classid_ISamplePlugin ){
		pvObject = ( ISamplePlugin* )this;
	}
	else if( classid == classid_ISampleSaveLoadCallback ){
		pvObject = ( ISampleSaveLoadCallback* )this;
	}
	else if( classid == classid_ISampleFooBar ){
		pvObject = ( ISampleFooBar* )this;
	}
	return	pvObject;
}
unsigned long	CSamplePlugin::AddRef(){
	return	++m_cRef;
}
unsigned long	CSamplePlugin::Release(){
	if( --m_cRef == 0 ){
		delete this;
		return	0;
	}
	return	m_cRef;
}
bool	CSamplePlugin::Save( ISampleSave*	pSave ){
	// 何かセーブする
	return true;
}
bool	CSamplePlugin::Load( ISampleLoad*	pLoad ){
	// 何かロードする
	return	true;
}
int CSamplePlugin::foo(){
	return	m_foo;
}
int CSamplePlugin::bar(){
	return	m_bar;
}

そしてマルチインターフェイスのオブジェクトを扱う側のコードでは RTTI を使って適宜必要な型を得てオブジェクトにアクセスするようにするのです
Fig.7

ISamplePlugin*	pPlugin = ...;
...
ISampleSaveLoadCallback*	pCallback = pPlugin->DynamicCast( classid_ISampleSaveLoadCallback );
if( pCallback ){
	// セーブ/ロード機能を持っているから任せる
	bool	fSuccess = pCallback->Save( pSave );
	if( fSuccess ){
		// ちゃんとセーブできた。
		...
	}
	else{
		// セーブに失敗した。何か適切なエラー処理をする
		...
	}
}
else{
	// セーブ/ロード機能を持っていないからデフォルトの処理をする
	...
}

こうすることでユーザーは必要な機能だけを実装すればよくなり、それを使う側はユーザーに負担をかけずに機能を拡張することができるようになるのです