組み込みC/C++

C/C++リテラシー向上のためのページ

case文を消したい②

前回に引き続きcase文の回避方法です。今回は多相性(多様性多態性、どれが正しい表現かわかりませんが関数のオーバーロードを使います)を使ってみたいと思います。

 

リファクタリング対象のコードは下記になります。

 

cliant.cpp

#include <iostream>
#include <conio.h>
#include <time.h>
#include <windows.h>

#define   EnEventKeybord 0
#define   EnEventTimer   1
#define   EnEventMouse   2

struct StOption{
	int i_opt1;
	int i_opt2;
};

void KeybordOperate(void);
void TimerOperate(void);
void MouseOperate(void);
bool ConsoleReceiveEvent(unsigned char enEvent, struct StOption stOption);

int _tmain(int argc, _TCHAR* argv[ ])
{
	while(1)
	{
		KeybordOperate();
		TimerOperate();
		MouseOperate();
	}

	return 0;
}

void KeybordOperate(void)
{
	struct StOption stOption;

	if(kbhit()){
		stOption.i_opt1 = getch();
		if( (0x30 <= stOption.i_opt1) && (stOption.i_opt1 <= 0x80)){
			ConsoleReceiveEvent(EnEventKeybord, stOption);
		}
	}
}

void TimerOperate(void)
{
	static bool b_tflag = true;
	struct StOption stOption ;
	time_t timer;
	struct tm *local;

	timer = time(NULL);
	local = localtime(&timer);
	if( !(local->tm_sec % 3) ){
		if(b_tflag){
			stOption.i_opt1 = 3;
			ConsoleReceiveEvent(EnEventTimer, stOption );
			b_tflag = false;
		}
	}else{
		b_tflag = true;
	}
}

void MouseOperate(void)
{
	struct StOption stOption;
	static POINT old_po;
	POINT po;
	GetCursorPos( &po );

	if( old_po.x != po.x || old_po.y != po.y ){
		stOption.i_opt1 = po.x;
		stOption.i_opt2 = po.y;
		ConsoleReceiveEvent(EnEventMouse, stOption);
		old_po = po;
	}
}

bool ConsoleReceiveEvent(unsigend char enEvent, struct StOption stOption)
{
	switch(enEvent){	
	case EnEventKeybord:
		std::cout << "Keybord -> " << (char)stOption.i_opt1 << std::endl;
		break;
	case EnEventTimer:
		std::cout << "Timer -> " << stOption.i_opt1 << std::endl;
		break;
	case EnEventMouse:
		std::cout << "Mouse x-> " << stOption.i_opt1 << " y->" << stOption.i_opt2 << std::endl; 
		break;
	default:
		break;
	}

	return 0;
}

 

シングルタスクのプログラムでは疑似的にマルチタスク処理を行うためMainループで各種役割を持ったオブジェクトを呼びだして、あたかもパラレルで処理が動いているかのように見せます。前回に引き続きこういった形の処理はゲームや組み込みでは一般的かと思います。KeybordOperate(キー入力)、TimerOperate(割り込み風のタイマー)、MouseOperate(マウスの座標取得)というイベント発行プログラムからのイベントを集約してConsoleReceiveEventで出力の処理をしています。イベント発行プログラムは何でもかまいません。組み込みを想定していますので、通常は割り込み処理でフラグを立てていると考えて下さい。なおイベントドリブン型のプログラムでも出力処理を集約してswitch文で処理するプログラムはよく見られるものだと思います。では、このプログラムのcase文を消し込もうかと思います。ます出力処理をクラス化します。

 

WriteConsoleTask.h

#pragma once
class WriteConsoleTask
{
public:
    virtual void func(void)=0;
protected:
};

class WriteKeybordTask : public WriteConsoleTask
{
public:
	WriteKeybordTask(int iKey):m_iKey(iKey){}
	virtual ~WriteKeybordTask(){}
	void func(void);
private:
	int m_iKey;
};

class WriteTimerTask : public WriteConsoleTask
{
public:
	WriteTimerTask(int iTime):m_iTime(iTime){}
	virtual ~WriteTimerTask(){}
	void func(void);
private:
	int m_iTime;
};

class WriteMouseTask : public WriteConsoleTask
{
public:
	WriteMouseTask(int ix, int iy):m_ix(ix), m_iy(iy){}
	virtual ~WriteMouseTask(){}
	void func(void);
private:
	int m_ix;
	int m_iy;
};

 

WriteConsoleTask.cpp

#include "WriteConsoleTask.h"
#include <iostream>

void WriteKeybordTask::func(void){
    std::cout << "Keybord -> " << (char)m_iKey << std::endl;
}

void WriteTimerTask::func(void){
	std::cout << "Timer -> " << m_iTime << std::endl;
}

void WriteMouseTask::func(void){
	std::cout << "Mouse x-> " << m_ix << " y->" << m_iy << std::endl; 
}

 

インターフェースであるWriteConsoleTaskを継承してクラスを準備しました。cliant側の変更は下記となります。

 

cliant.cpp

#include <conio.h>
#include <time.h>
#include <windows.h>
#include "WriteConsoleTask.h"

void KeybordOperate(void);
void TimerOperate(void);
void MouseOperate(void);
bool ConsoleReceiveEvent(WriteConsoleTask *c_wct);

int _tmain(int argc, _TCHAR* argv[])
{
    while(1)
	{
		KeybordOperate();
		TimerOperate();
		MouseOperate();
	}

	return 0;
}

void KeybordOperate(void)
{
	int iKey;

	if(kbhit()){
		iKey = getch();
		if( (0x30 <= iKey) && (iKey <= 0x80)){
			WriteKeybordTask c_wkt(iKey);
			ConsoleReceiveEvent( (WriteConsoleTask *)&c_wkt );
		}
	}
}

void TimerOperate(void)
{
	static bool b_tflag = true;
	time_t timer;
	struct tm *local;

	timer = time(NULL);
	local = localtime(&timer);
	if( !(local->tm_sec % 3) ){
		if(b_tflag){
			WriteTimerTask c_wtt(3);
			ConsoleReceiveEvent( (WriteConsoleTask *)&c_wtt );
			b_tflag = false;
		}
	}else{
		b_tflag = true;
	}
}

void MouseOperate(void)
{
	static POINT old_po;
	POINT po;
	GetCursorPos( &po );

	if( old_po.x != po.x || old_po.y != po.y ){
		WriteMouseTask c_wmt(po.x, po.y);
		ConsoleReceiveEvent( (WriteConsoleTask *)&c_wmt );
		old_po = po;
	}
}

bool ConsoleReceiveEvent(WriteConsoleTask * c_wct)
{
	c_wct->func();

	return 0;
} 

 

出力部分のConsoleReceiveEventのcase文が消えました。大分行数は増えてしまいましたが、以後イベントを追加した場合には出力処理部分は気にすることなくクラスの設計とクラスの生成に集中できる形になったのではないでしょうか。また組み込みを想定していますのでnewは使っていません。Localで生成したObjectを渡していますので生存期間には注意が必要です。引数を渡す時にインターフェースの型にcastして渡す事でcase文が消えていますので多相性の理解に役立ててください。なおFactoryMethodでcase文を消す場合もパターンとしてはこのパターンと同じです。この方法はFactoryMethodの一側面を基にしています。多相性を使ってcase文を消せるパターンは「元々分岐していたイベントを集約する過程でswitch文を使って再分岐させるパターン」に限定されます。何もないところから分岐するには、テーブルを使用するか連想配列を使うしかcase文を消すのは難しいかと思います。