BASIC OOP ~Vol.5~

BASIC OOP ~Vol.5~

Strategy

概要

  • サブクラスの挙動を特化させ動的に切り替える

解説

まずオブジェクトのスーパークラス、サブクラスの定義です。
インデックスを保持するシンプルなものになっています。

//	オブジェクトスーパークラス
class IObject
{
protected:
	IObject() = default;
public:
	virtual ~IObject() = default;
public:
	virtual int GetIndex() const = 0;
};
//	オブジェクトサブクラス
class CObject final : public IObject
{
public:
	CObject(uint32_t index) : IObject(), m_uIndex(index) {}
	virtual ~CObject() = default;
public:
	virtual int GetIndex() const
	{
		return m_uIndex;
	}
public:
	uint32_t m_uIndex;
};

さらに管理クラスを定義し検索メソッドを実装しました。

//	オブジェクト管理クラス
class CObjectManager final
{
public:
	CObjectManager() : m_Objects() {}
	~CObjectManager()
	{
		for (auto it = m_Objects.begin();
			it != m_Objects.end();
			++it)
		{
			delete (*it);
		}
		m_Objects.clear();
	}
public:
	//	全条件をパスするオブジェクトを検索
	//	rResult		-条件をパスしたオブジェクトを受け取るリスト
	//	crConditions	-条件リスト
	bool Match(std::list<IObject*>& rResult, const std::list<ICondition*>& crConditions)
	{
		rResult.clear();
		//
		for (auto it = m_Objects.begin();
			it != m_Objects.end();
			++it)
		{
			if (MatchConditions((*it), crConditions))
			{
				rResult.push_back((*it));
			}
		}
		return !rResult.empty();
	}
private:
	//	オブジェクトの全条件パス判定
	//	pObject		-条件を判定するオブジェクト
	//	crConditions	-条件リスト
	bool MatchConditions(IObject* pObject, const std::list<ICondition*>& crConditions)
	{
		for (auto it = crConditions.begin();
			it != crConditions.end();
			++it)
		{
			if (!(*it)->Match(pObject))
			{
				return false;
			}
		}
		return true;
	}
public:
	void AppendObject(IObject* pObject)
	{
		m_Objects.push_back(pObject);
	}
private:
	std::list<IObject*> m_Objects;
};

注目して欲しいのは検索メソッドの実装方法で
この部分がStrategyパターンになります。

引数で受け取った[crConditions]のMatchメソッドに対して
全て真を返すオブジェクトを[rResult]に格納します。

ポイントとなるIConditionの定義です。

//	条件インターフェース
class ICondition
{
protected:
	ICondition() = default;
public:
	virtual ~ICondition() = default;
public:
	//	引数オブジェクトの条件パス判定
	virtual bool Match(const IObject* pObject) const = 0;
};

Matchメソッドはオブジェクトが条件に一致していた場合に真を返します。
その条件はサブクラス側の実装で自由に設定可能です。

オブジェクトのインデックスと任意値の比較結果を検索条件にしたいので
それぞれ必要なサブクラスを定義してみました。

//	数値条件スーパークラス
class CValueCondition : public ICondition
{
protected:
	CValueCondition(int32_t value) : m_iValue(value) {}
public:
	virtual ~CValueCondition() = default;
protected:
	int32_t GetValue() const { return m_iValue; }
private:
	int32_t m_iValue;
};
//	条件サブクラス:等しい
class CEQUCondition final : public CValueCondition
{
public:
	CEQUCondition(int32_t value) : CValueCondition(value) {}
	virtual ~CEQUCondition() = default;
public:
	virtual bool Match(const IObject* pObject) const { return GetValue() == pObject->GetIndex(); }
};
//	条件サブクラス:等しくない
class CNEQCondition final : public CValueCondition
{
public:
	CNEQCondition(int32_t value) : CValueCondition(value) {}
	virtual ~CNEQCondition() = default;
public:
	virtual bool Match(const IObject* pObject) const { return GetValue() != pObject->GetIndex(); }
};
//	条件サブクラス:未満
class CLSSCondition final : public CValueCondition
{
public:
	CLSSCondition(int32_t value) : CValueCondition(value) {}
	virtual ~CLSSCondition() = default;
public:
	virtual bool Match(const IObject* pObject) const { return GetValue() > pObject->GetIndex(); }
};
//	条件サブクラス:以下
class CLEQCondition final : public CValueCondition
{
public:
	CLEQCondition(int32_t value) : CValueCondition(value) {}
	virtual ~CLEQCondition() = default;
public:
	virtual bool Match(const IObject* pObject) const { return GetValue() >= pObject->GetIndex(); }
};
//	条件サブクラス:超過
class CGTRCondition final : public CValueCondition
{
public:
	CGTRCondition(int32_t value) : CValueCondition(value) {}
	virtual ~CGTRCondition() = default;
public:
	virtual bool Match(const IObject* pObject) const { return GetValue() < pObject->GetIndex(); }
};
//	条件サブクラス:以上
class CGEQCondition final : public CValueCondition
{
public:
	CGEQCondition(int32_t value) : CValueCondition(value) {}
	virtual ~CGEQCondition() = default;
public:
	virtual bool Match(const IObject* pObject) const { return GetValue() <= pObject->GetIndex(); }
};

実際に利用してみます。

int main()
{
	CObjectManager manager;
	//	適当に追加しておく
	for (int i = 0; i < 10; ++i)
	{
		manager.AppendObject(new CObject(i));
	}
	//
	//	indexが5以上で8以外のものを検索条件に
	//
	std::list<ICondition*> conditions;
	static CGEQCondition g_GEQ = CGEQCondition(5);
	static CNEQCondition g_NEQ = CNEQCondition(8);
	conditions.push_back(&g_GEQ);
	conditions.push_back(&g_NEQ);
	//
	//	探す
	std::list<IObject*> result;
	manager.Match(result, conditions);
	//	結果を表示
	for (auto it = result.begin(); it != result.end(); ++it)
	{
		std::cout << (*it)->GetIndex() << std::endl;
	}
	//
	return EXIT_SUCCESS;
}

実行結果

5
6
7
9

このようにサブクラスを条件やアルゴリズム単位で特化させ
動的に切り替えるのがStrategyパターンになります。

条件追加の際はサブクラス追加のみで済みますし
オブジェクト管理クラスへの影響も最小限です。

Strategyパターンで作成しなかった場合では
保守が困難な状態に陥る可能性が上がるでしょう。
(検索関数が条件によって増えたり引数が増えたり…)

OOP言語における関数オブジェクト※1・文字通り関数をオブジェクトとしてみなす
・ラムダ式もこれに分類される
の概念と類似しており
実用性が高いパターンですので是非覚えて頂きたいです。

組み合わせや実行順序の変更にも柔軟性があるので
複雑な条件分岐やアルゴリズムを伴うゲーム系と相性が良いように思えます。

  • AIのトリガ(索敵、アラーム)
  • UIの項目フィルター、ソート
  • ミッション達成条件
  • アイテム、魔法の効果適用

辺りを実装する際には検討してみて下さい。

脚注

↑ 1. ・文字通り関数をオブジェクトとしてみなす
・ラムダ式もこれに分類される

Tags: