0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

オペレーションに応じた分岐をState&Factoryパターンで可読性を向上する

Last updated at Posted at 2024-07-27

概要

WindowsのGUIアプリでモードによって処理を切り替えたいとき、Stateパターンを用いることで分岐処理を省略することができました。
知識しかなかったデザインパターンの有効性を強く感じて、嬉しくなったので記事にしています。

良くなったと思う点

1. 動作が増えたときにSwitch文を書く必要がなく、ソースコードの量が減る
2. モードが増えたときにすべてのSwitch分に追加する必要がなく、Factoryに追加するだけで済む
3. 共通処理を継承元に置くことで、switchの分岐をまとめるよりも共通化が行いやすい

改善したいコード

例として、クリックした点を使用した図形描画を考えます。
点、三角形、四角形などは必要な点数が異なるため、クリックされたときに単純に分岐を書くとパターンが膨大になってしまいます。
また、行いたい動作事に分岐が必要になりソースコードの量が増え、モードを追加したときの変更箇所が増えてしまいます。

#pragma region UI部分
class BadUIManager
{
public:
	enum eMode
	{
		POINT,
		TRIANGLE,
		RECT
	};
	void ChangeMode(int listIdx);
	void OperationClick(CPoint pt);
	void OperationMouseMove(CPoint pt);
	void OperationBack();
	void OperationRestart();

private:
	eMode _mode;
	strConfig _config;
	vector<CPoint> _pts;
};

//モード変更したとき
inline void BadUIManager::ChangeMode(int listIdx)
{
	_mode = static_cast<eMode>(listIdx);
}

//クリックされたとき
inline void BadUIManager::OperationClick(CPoint pt)
{
	_pts.push_back(pt);
	switch (_mode)
	{
	case BadUIManager::POINT:
		//点の描画処理
		break;
	case BadUIManager::TRIANGLE:
		if (_pts.size() == 3) {
			//三角形の描画登録処理
			_pts.clear();
		}
		break;
	case BadUIManager::RECT:
		if (_pts.size() == 4) {
			//四角形の描画登録処理
			_pts.clear();
		}
		break;
	default:
		break;
	}

}

//マウスが動いたとき
inline void BadUIManager::OperationMouseMove(CPoint pt)
{
	switch (_mode)
	{
	case BadUIManager::TRIANGLE:
     //三角形の仮描画処理
		break;
	case BadUIManager::RECT:
     //四角形の仮描画処理
		break;
	case BadUIManager::POINT:
	default:
		//点モードでは仮描画を行わない
		break;
	}
}

//戻るを押したとき
inline void BadUIManager::OperationBack()
{
	_pts.pop_back();
	//モードによる分岐が必要ならswitchが必要
}

//やり直しのとき
inline void BadUIManager::OperationRestart()
{
	//やり直し処理
	//モードによる分岐が必要ならswitchが必要
}
#pragma endregion

改善したソースコード

1. インターフェースに各動作(クリック、やり直し、初期化など)をメソッドとして定義します。
2. インターフェースを継承した基底クラスを定義し、共通の動作を記述します。
3. モードによって振る舞いが異なる動作に関してはOverrideすることで異なる処理のみを変更することができます。
4. モードが切り替わった際には、モードに応じたEditorインスタンスを作成するFactoryメソッドをUI側で呼び出します。

クラス図

クラス図.JPG

Editor部分

using namespace std;
//UIから受け取る共通設定
struct strConfig
{
	int lineWidth;
	COLORREF color;
};

//共通処理のインターフェース
class IShapeEditorInterface
{
public:
	virtual void ClickedOperatin(CPoint pt)=0;
	virtual void OperationBack()=0;
	virtual void OperationRestart()=0;
	virtual void MouseMove(CPoint pt) = 0;
};

//図形描画の基底クラス
class ShapeEditorBase:public IShapeEditorInterface
{
public:
	enum eMode
	{
		POINT,
		TRIANGLE,
		RECT,
	};
	ShapeEditorBase(strConfig pConfig);
	virtual void ClickedOperatin(CPoint pt)override {};
	virtual void OperationBack()override;
	virtual void OperationRestart()override;
	virtual void MouseMove(CPoint pt)override {};
	static unique_ptr<ShapeEditorBase> GetShapeEditorFactory(eMode mode, strConfig pConfig);
protected:
	strConfig _spConfig;//受け取った共通設定
	vector<CPoint> _ClickPts;//クリックされたWindow座標の格納
};

#pragma region 全図形の共通処理
//
ShapeEditorBase::ShapeEditorBase(strConfig pConfig)
{
	_spConfig = pConfig;//共通のオプションを受け取る
}

inline void ShapeEditorBase::OperationBack()
{
	_ClickPts.pop_back();
}

inline void ShapeEditorBase::OperationRestart()
{
	_ClickPts.clear();
}
#pragma endregion //全図形の共通処理

#pragma region 点描画
class ShapePointEdtor:public ShapeEditorBase
{
public:
	ShapePointEdtor(strConfig pConfig);

private:
	virtual void ClickedOperatin(CPoint pt)override ;
};

ShapePointEdtor::ShapePointEdtor(strConfig pConfig):ShapeEditorBase(pConfig)
{
}

inline void ShapePointEdtor::ClickedOperatin(CPoint pt)
{
	_ClickPts.push_back(pt);
	//点の場合はすぐに描画する
}
#pragma endregion 


#pragma region 三角形描画
class ShapeTriangleEditor:public ShapeEditorBase
{
public:
	ShapeTriangleEditor(strConfig pConfig);
	virtual void ClickedOperatin(CPoint pt)override;
	virtual void MouseMove(CPoint pt)override ;

private:

};

ShapeTriangleEditor::ShapeTriangleEditor(strConfig pConfig) :ShapeEditorBase(pConfig)
{
}

inline void ShapeTriangleEditor::ClickedOperatin(CPoint pt)
{
	_ClickPts.push_back(pt);
	if (_ClickPts.size() == 3) {
		//描画と登録処理を行う
		_ClickPts.clear();
	}
}

inline void ShapeTriangleEditor::MouseMove(CPoint pt)
{
	if (_ClickPts.size() < 1)
		return;
	//2点目からは仮描画を行う
	//仮描画処理
}
#pragma endregion

#pragma region 四角形
class ShapeRectEditor:public ShapeEditorBase
{
public:
	ShapeRectEditor(strConfig pConfig);
	virtual void ClickedOperatin(CPoint pt)override;
	virtual void MouseMove(CPoint pt)override;
private:

};

ShapeRectEditor::ShapeRectEditor(strConfig pConfig) :ShapeEditorBase(pConfig)
{
}

inline void ShapeRectEditor::ClickedOperatin(CPoint pt)
{
	_ClickPts.push_back(pt);
	if (_ClickPts.size() == 4) {
		//描画と登録処理を行う
		_ClickPts.clear();
	}
}

inline void ShapeRectEditor::MouseMove(CPoint pt)
{
	if (_ClickPts.size() < 1)
		return;
	//2点目からは仮描画を行う
	//仮描画処理
}

#pragma endregion

inline unique_ptr<ShapeEditorBase> ShapeEditorBase::GetShapeEditorFactory(eMode mode, strConfig pConfig)
{
	switch (mode)
	{
	case ShapeEditorBase::POINT:
		return make_unique<ShapePointEdtor>(pConfig);
	case ShapeEditorBase::TRIANGLE:
		return make_unique<ShapePointEdtor>(pConfig);
	case ShapeEditorBase::RECT:
		return make_unique<ShapePointEdtor>(pConfig);
	default:
		return nullptr;//ここで例外処理を行うのも良し
	}
}

UI部分

UI部分のリストボックスやボタンでモード切替ややり直しを行うことを想定しています。
処理をEditorクラスに委譲することで分岐処理がなくなり、コードが見やすくなったのではないでしょうか。

#pragma region UI部分
class UIManager
{
public:
	void ChangeMode(int listIdx);
	void OperationClick(CPoint pt);
	void OperationMouseMove(CPoint pt);
	void OperationBack();
	void OperationRestart();

private:
	unique_ptr<IShapeEditorInterface> _upShapeEditor;
	strConfig _config;
};

//モード変更したとき
inline void UIManager::ChangeMode(int listIdx)
{
	if (_upShapeEditor)
		_upShapeEditor.reset();
	_upShapeEditor = ShapeEditorBase::GetShapeEditorFactory(static_cast<ShapeEditorBase::eMode>(listIdx), _config);
}

//クリックされたとき
inline void UIManager::OperationClick(CPoint pt)
{
	_upShapeEditor->ClickedOperatin(pt);
}

//マウスが動いたとき
inline void UIManager::OperationMouseMove(CPoint pt)
{
	_upShapeEditor->MouseMove(pt);
}

//戻るを押したとき
inline void UIManager::OperationBack()
{
	_upShapeEditor->OperationBack();
}

//やり直しのとき
inline void UIManager::OperationRestart()
{
	_upShapeEditor->OperationRestart();
}
#pragma endregion
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?