Здравствуйте
Давно хотел начать писать свой блог, но никак не мог придумать с чего начать.
Проблема о которой я напишу - не надуманная, а вполне реальная. Выглядит она так:
- class SomeMenu : public Menu
- {
- ...
- virtual void Command( const char* command, const char* arg )
- {
- if ( strcmp( command, "Command1" ) == 0 )
- {
- // some commands
- }
- else if ( strcmp( command, "Command2" ) == 0 )
- {
- // ...
- }
- else if ( ... )
- {
- ...
- }
- ......
- else
- {
- Menu::Command( command, arg );
- }
- }
- };
Уверен, что многие читающие не видят в этом проблему и отчасти они правы. Если условий не больше 10 и в каждом по 2-3 команды, то такой код, действительно, очень прост и довольно легко поддерживается. А теперь представьте, что условий стало больше 50-100 и в половине из них больше десятка строк, а код подлежит дальнейшей модификации и поиску ошибок...
Сразу становится понятно, что нужно что-то делать...
Для решения этой проблемы, я написал такой шаблон:
- template<typename T>
- class DefaultCompareFn
- {
- public:
- bool operator ()(T op1, T op2) const
- {
- return op1 == op2;
- }
- };
- template<typename Key, typename Value, class KeyCompare = DefaultCompareFn<Key> >
- class Factory
- {
- Value mResult;
- KeyCompare mCompFn;
- public:
- struct Item
- {
- Key key;
- Value value;
- };
- Factory( const Item* items, size_t size, Key key, Value defaultValue ) :
- mResult(defaultValue), mCompFn()
- {
- for( size_t i = 0; i < size; i++ )
- {
- if (mCompFn(items[i].key, key))
- {
- mResult = items[i].value;
- break;
- }
- }
- }
- operator Value()
- {
- return mResult;
- }
- };
Применяется это так:
- struct strcomparer
- {
- bool operator()( const char* str1, const char* str2 ) const
- {
- return strcmp( str1, str2 ) == 0;
- }
- };
- class SomeMenu : public Menu
- {
- ...
- typedef void (SomeMenu::*CommandFn)(const char*);
- typedef Factory<const char*, SomeMenu::CommandFn, strcomparer> SomeMenuFactory;
- static const SomeMenuFactory::Item sCommandHandlers[];
- static const size_t sCommandHandlersSize;
- void Command1(const char* arg)
- {
- ...
- }
- void Command2(const char* arg)
- {
- ...
- }
- void Command(const char* command, const char* arg)
- {
- CommandFn fn = SomeMenuFactory( sCommandHandlers, sCommandHandlersSize, command, 0 );
- if (fn != 0)
- {
- (this->*fn)(arg);
- }
- else
- {
- Menu::Command( command, arg );
- }
- }
- };
- const SomeMenu::SomeMenuFactory::Item SomeMenu::sCommandHandlers[] =
- {
- { "Command1", &SomeMenu::Command1 },
- { "Command2", &SomeMenu::Command2 },
- };
- const size_t SomeMenu::sCommandHandlersSize = sizeof(SomeMenu::sCommandHandlers)/sizeof(SomeMenu::sCommandHandlers[0]);
Но у этого кода есть недостаток - функции CommandX не могут быть виртуальными или расположенными вне класса. Надеюсь в будущем устранить этот недостаток...
Встречал в JavaScript такой интересный изврат:
ОтветитьУдалитьswitch(true)
{
case str == "abc":
// ...
break;
case str == "xyz":
// ...
break;
case str == "123":
// ...
break;
}
Жаль в C/C++ такое не прокатывает((
Этот комментарий был удален автором.
ОтветитьУдалить// SomeMenu private section
ОтветитьУдалитьtypedef std::map< std::string, boost::function > handler_container_t;
handler_container_t handlers_;
SomeMenu::BindCommandHandlers()
{
handlers_["Command1"] = boost::bind(&SomeMenu::OnCommand1, this, _1);
handlers_["Command2"] = boost::bind(&SomeMenu::OnCommand2, this, _1);
}
void SomeMenu::OnCommand1(const char* args)
{
//...
}
void SomeMenu::OnCommand2(const char* args)
{
//...
}
void SomeMenu::Command(const char* command, const char* arg)
{
assert(command && arg && "Bad args!");
handler_container_t::const_iterator
it = handlers_.find(command);
if (it != handlers_.end() && it->second) {
it->second();
}
}
// как вставлять по-человечески код? блоггер форматирование калечит ужс
Соглашусь с Иваном - сам хотел вариант с boost::bind предложить.
ОтветитьУдалитьИван, а не пугает наличие в каждом классе абсолютно одинаковой таблицы функций?
ОтветитьУдалить