組み込みC/C++

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

case文を消したい③

次のプログラムを見てください。

 

cliant.cpp

#include "stdafx.h"
#include <conio.h>
#include <iostream>

void sequenceTask(void);

unsigned int iTask = 0;

int _tmain(int argc, _TCHAR* argv[ ] )
{
    iTask = 1;

	while(iTask){
		sequenceTask();
	}

	return 0;
}

void sequenceTask(void){
	
	int iKey;

	switch(iTask){
	case 1:
		std::cout << "case 1 キーボード入力によって遷移先が変わります" << std::endl;
		iTask ++;
	case 2:
		if(kbhit()){
			iKey = getch();
			if(iKey < 0x40){
				std::cout << "case 2 次はcase 3だよ" << std::endl;
				iTask = 3;
			}else{
				std::cout << "case 2 次はcase 4だよ" << std::endl;
				iTask = 4;
			}
			iKey = 0;
		}
		break;
	case 3:
		if(kbhit()){
			iKey = getch();
			if(iKey < 0x40){
				std::cout << "case 3 次はcase 4だよ" << std::endl;
				iTask = 4;
			}else{
				std::cout << "case 3 残念 次はcase 1だよ" << std::endl;
				iTask = 1;
			}
			iKey = 0;
		}
		break;
	case 4:
		if(kbhit()){
			iKey = getch();
			if(iKey < 0x40){
				std::cout << "case 4 終了です" << std::endl;
				iTask = 0;
			}else{
				std::cout << "case 4 残念 次はcase 1だよ" << std::endl;
				iTask = 1;
			}
			iKey = 0;
		}
		break;
	default:
		break;
	}
}

 

組み込み現場で良く見られる処理構造で、主に外部デバイスとやりとりが発生する場合に用いられます。シーケンス処理とかタスク処理なんて呼ばれているのではないでしょうか。基本は逐次case文を処理していきますが、時によって遷移先が変化します。今回の例ではユーザのキーボード入力に応じて遷移先を分岐しています。この手のプログラムの何が嫌かというと仕様変更等でcase文の間に処理を追加する必要があった時、その影響をくまなく調べなければいけないという事です。そこでこのcase文一つ一つを状態とみなし、オブジェクトを作成することで仕様追加に強いプログラムにリファクタリングしたいと思います。以下がリファクタリング後のソースとなります。

 

State.h

#pragma once

class State{
public:
    virtual State * func() = 0;
	static State * getFirstState();
};

class FirstState : public State
{
public:
	State * func();
};
class SecondState : public State
{
public:
	State * func();
};

class ThirdState : public State
{
public:
	State * func();
};

class FourthState : public State
{
public:
	State * func();
};    

 

State.cpp

#include "State.h"
#include <conio.h>
#include <iostream>

static FirstState state1;
static SecondState state2;
static ThirdState state3;
static FourthState state4;

State *State::getFirstState(){
    return(&state1);
}

State *FirstState::func(){
	std::cout << "case 1 キーボード入力によって遷移先が変わります" << std::endl;
	return(&state2);
}

State *SecondState::func(){
	int iKey=0;
	if(kbhit()){
		iKey = getch();
		if(iKey < 0x40){
			std::cout << "case 2 次はcase 3だよ" << std::endl;
			return(&state3);
		}else{
			std::cout << "case 2 次はcase 4だよ" << std::endl;
			return(&state4);
		}
	}
	return(&state2);
}

State *ThirdState::func(){
	int iKey=0;
	if(kbhit()){
		iKey = getch();
		if(iKey < 0x40){
			std::cout << "case 3 次はcase 4だよ" << std::endl;
			return(&state4);
		}else{
			std::cout << "case 3 残念 次はcase 1だよ" << std::endl;
			return(&state1);
		}
	}
	return(&state3);
}

State *FourthState::func(){
	int iKey=0;
	if(kbhit()){
		iKey = getch();
		if(iKey < 0x40){
			std::cout << "case 4 終了です" << std::endl;
			return(0);
		}else{
			std::cout << "case 4 残念 次はcase 1だよ" << std::endl;
			return(&state1);
		}
	}
	return(&state4);
}

 

cliant.cpp

#include "stdafx.h"
#include "State.h"

int _tmain(int argc, _TCHAR* argv[])
{
    static State *state= State::getFirstState();

	while(state != NULL ){
		state = state->func();
	}

	return 0;
}

 

StatePatternを応用したプログラムです。各状態を表すオブジェクトが遷移後の状態オブジェクトを返す事によりcase文を消しています。実際のStatePatternはFactoryMethodと同様newしたオブジェクトを返すのですが、組み込み現場においてはnewを使えなかったりしますのでstaticとして宣言しオブジェクトをファイル内に閉じ込めます。 なおnewできない環境ではnewしていたオブジェクトはstatic変数として扱う事になります。ずっとRAMにいる状況となりますがフラグメントの発生によりシステム停止になるよりかは安定しているとみなします。もちろんStackで実現できるのであればStackを使う方法を検討しますが、今回のようにスコープが外れてしまうようなプログラムではstaticでオブジェクトを宣言する必要があります。これでもglobalは出来るだけ避けています。

プログラムの仕様変更が発生した場合ですが、クラスとして新しい状態を独立して作成できるため全体への影響は少なくなったかと思います。ただし、やはりこういったプログラムはいつもそうなのですが、単純な構造に対してはcase文を追加した方が簡単ですしROMも食いません。状況に応じて検討してみてください。