case文を消したい①
case文といえば後の変更がわずらわしい悪の権化のように語られます。そんなわけでcase文の消し方を紹介しようかと思います。
リファクタリング対象のコードは以下のようなコードです。
#include <iostream> #include <conio.h> void writeWhiteTask(void); void writeBlueTask(void); void writeRedTask(void); void writeYellowTask(void); #define EnEightKey 0x38 #define EnFourKey 0x34 #define EnSixKey 0x36 #define EnTwoKey 0x32 int _tmain(int argc, _TCHAR * argv[ ] ) { int iKey; while(1) { if(kbhit()){ iKey = getch(); switch(iKey){ case EnEightKey: writeWhiteTask(); break; case EnFourKey: writeBlueTask(); break; case EnSixKey: writeRedTask(); break; case EnTwoKey: writeYellowTask(); break; default: break; } } } return 0; } void writeWhiteTask(void) { std::cout << "White" << std::endl; } void writeBlueTask(void) { std::cout << "Blue" << std::endl; } void writeRedTask(void) { std::cout << "Red" << std::endl; } void writeYellowTask(void) { std::cout << "Yellow" << std::endl; }
ゲームも組み込みシステムも世にあるありとあらゆるプログラムは上記のような形をしていますね。これはテンキーの入力に対応してコンソールに文字列を出すプログラムになります。いい悪いは別の話しとして、このcase文を消すリファクタリングを紹介します。なおこのプログラムに対してFactoryMethodは有効ではありません。FactoryMethodでのcase文の消去法はかなり限られたパターンにしか適応できません。そちらはまたの機会に紹介しようかと思います。
・連想コンテナを使ったcase文の消去
#include <iostream> #include <map> #include <conio.h> void writeWhiteTask(void); void writeBlueTask(void); void writeRedTask(void); void writeYellowTask(void); #define EnEightKey 0x38 #define EnFourKey 0x34 #define EnSixKey 0x36 #define EnTwoKey 0x32 int _tmain(int argc, _TCHAR * argv[ ] ) { typedef void (*FuncProc)(void); int iKey; std::map<unsigned char, FuncProc> jumpMap; jumpMap.insert( std::make_pair(EnEightKey, writeWhiteTask)); jumpMap.insert( std::make_pair(EnFourKey, writeBlueTask)); jumpMap.insert( std::make_pair(EnSixKey, writeRedTask)); jumpMap.insert( std::make_pair(EnTwoKey, writeYellowTask)); while(1) { if(kbhit()){ iKey = getch(); jumpMap[(unsigned char)iKey](); } } return 0; } void writeWhiteTask(void) { std::cout << "White" << std::endl; } void writeBlueTask(void) { std::cout << "Blue" << std::endl; } void writeRedTask(void) { std::cout << "Red" << std::endl; } void writeYellowTask(void) { std::cout << "Yellow" << std::endl; }
mapをファンクタとして使用する事で入力キーに対してcase文を使用することなく関数Jumpが実現できました。後に関数を追加する場合もmapに関数を登録するだけで変更が可能です。この方法の他にも関数Jumpテーブル(関数を要素に持つ配列)を使用する方法がありますが、入力(キーボードの入力)の値が連続的な値ではないので配列の値と入力値を対応させる為に変換テーブルが必要となり、後の変更量が多いコードになってしまいます。オブジェクティブという意味では有効ではありません。
なのですが、このコードには問題があります。
①mapが動的領域確保を行っている。
②キーから値を取り出すのに、内部的にはiteratorを回している為パフォーマンスが悪い。
①②ともPCのパッケージソフトでは許容されるものかと思いますが、特に①に関して組み込みでは厳しい壁になります。今でも組み込みシステムの多くはOSがありません。またパッケージソフトと違い、組み込みは電源が入っている限り永遠と動作してしまうプログラムです。動的なメモリ確保に対して断片化が起こりやすい上、ガベージコレクションをしてくれるOSがないためHeap領域の運用には慎重にならざるを得ません。C++の威力を発揮するためにはフレームワークとSTLが不可欠ですがそれらは動的メモリを前提としている為、組み込みシステムにおいてC++導入が進まない要因となっているようです。