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

# 概要
WindowsのGUIアプリでモードによって処理を切り替えたいとき、Stateパターンを用いることで分岐処理を省略することができました。
知識しかなかったデザインパターンの有効性を強く感じて、嬉しくなったので記事にしています。
# 良くなったと思う点
1\. 動作が増えたときにSwitch文を書く必要がなく、ソースコードの量が減る
2\. モードが増えたときにすべてのSwitch分に追加する必要がなく、Factoryに追加するだけで済む
3\. 共通処理を継承元に置くことで、switchの分岐をまとめるよりも共通化が行いやすい
# 改善したいコード
例として、クリックした点を使用した図形描画を考えます。
点、三角形、四角形などは必要な点数が異なるため、クリックされたときに単純に分岐を書くとパターンが膨大になってしまいます。
また、行いたい動作事に分岐が必要になりソースコードの量が増え、モードを追加したときの変更箇所が増えてしまいます。
~~~ cpp
#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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257894/2b479c11-9f25-77b7-f92c-7323676f9f4e.jpeg)

## Editor部分
~~~ cpp
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クラスに委譲することで分岐処理がなくなり、コードが見やすくなったのではないでしょうか。
~~~ cpp
#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
~~~