суббота, 3 июля 2010 г.

Первая запись

Здравствуйте

Давно хотел начать писать свой блог, но никак не мог придумать с чего начать.

Проблема о которой я напишу - не надуманная, а вполне реальная. Выглядит она так:

  1. class SomeMenu : public Menu
  2. {
  3. ...
  4.     virtual void Command( const char* command, const char* arg )
  5.     {
  6.         if ( strcmp( command, "Command1" ) == 0 )
  7.         {
  8.             // some commands
  9.         }
  10.         else if ( strcmp( command, "Command2" ) == 0 )
  11.         {
  12.             // ...
  13.         }
  14.         else if ( ... )
  15.         {
  16.             ...
  17.         }
  18.         ......
  19.         else
  20.         {
  21.             Menu::Command( command, arg );
  22.         }
  23.     }
  24. };

Уверен, что многие читающие не видят в этом проблему и отчасти они правы. Если условий не больше 10 и в каждом по 2-3 команды, то такой код, действительно, очень прост и довольно легко поддерживается. А теперь представьте, что условий стало больше 50-100 и в половине из них больше десятка строк, а код подлежит дальнейшей модификации и поиску ошибок...

Сразу становится понятно, что нужно что-то делать...


Для решения этой проблемы, я написал такой шаблон:

  1. template<typename T>
  2. class DefaultCompareFn
  3. {
  4.     public:
  5.     bool operator ()(T op1, T op2) const
  6.     {
  7.         return op1 == op2;
  8.     }
  9. };
  10.  
  11. template<typename Key, typename Value, class KeyCompare = DefaultCompareFn<Key> >
  12. class Factory
  13. {
  14.     Value mResult;
  15.     KeyCompare mCompFn;
  16. public:
  17.     struct Item
  18.     {
  19.         Key key;
  20.         Value value;
  21.     };
  22.  
  23.     Factory( const Item* items, size_t size, Key key, Value defaultValue ) :
  24.         mResult(defaultValue), mCompFn()
  25.     {
  26.         for( size_t i = 0; i < size; i++ )
  27.         {
  28.             if (mCompFn(items[i].key, key))
  29.             {
  30.                 mResult = items[i].value;
  31.                 break;
  32.             }
  33.         }
  34.     }
  35.  
  36.     operator Value()
  37.     {
  38.         return mResult;
  39.     }
  40. };

Применяется это так:

  1. struct strcomparer
  2. {
  3.     bool operator()( const char* str1, const char* str2 ) const
  4.     {
  5.         return strcmp( str1, str2 ) == 0;
  6.     }
  7. };
  8.  
  9. class SomeMenu : public Menu
  10. {
  11.     ...
  12.     typedef void (SomeMenu::*CommandFn)(const char*);
  13.     typedef Factory<const char*, SomeMenu::CommandFn, strcomparer> SomeMenuFactory;
  14.     static const SomeMenuFactory::Item sCommandHandlers[];
  15.     static const size_t sCommandHandlersSize;
  16.  
  17.     void Command1(const char* arg)
  18.     {
  19.         ...
  20.     }
  21.  
  22.     void Command2(const char* arg)
  23.     {
  24.         ...
  25.     }
  26.  
  27.     void Command(const char* command, const char* arg)
  28.     {
  29.         CommandFn fn = SomeMenuFactory( sCommandHandlers, sCommandHandlersSize, command, 0 );
  30.         if (fn != 0)
  31.         {
  32.             (this->*fn)(arg);
  33.         }
  34.         else
  35.         {
  36.              Menu::Command( command, arg );
  37.         }
  38.     }
  39. };
  40.  
  41. const SomeMenu::SomeMenuFactory::Item SomeMenu::sCommandHandlers[] =
  42. {
  43.     { "Command1", &SomeMenu::Command1 },
  44.     { "Command2", &SomeMenu::Command2 },
  45. };
  46.  
  47. const size_t SomeMenu::sCommandHandlersSize = sizeof(SomeMenu::sCommandHandlers)/sizeof(SomeMenu::sCommandHandlers[0]);

Но у этого кода есть недостаток - функции CommandX не могут быть виртуальными или расположенными вне класса. Надеюсь в будущем устранить этот недостаток...

5 комментариев:

  1. Встречал в JavaScript такой интересный изврат:

    switch(true)
    {
    case str == "abc":
    // ...
    break;
    case str == "xyz":
    // ...
    break;
    case str == "123":
    // ...
    break;
    }

    Жаль в C/C++ такое не прокатывает((

    ОтветитьУдалить
  2. Этот комментарий был удален автором.

    ОтветитьУдалить
  3. // 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();
    }

    }

    // как вставлять по-человечески код? блоггер форматирование калечит ужс

    ОтветитьУдалить
  4. Соглашусь с Иваном - сам хотел вариант с boost::bind предложить.

    ОтветитьУдалить
  5. Иван, а не пугает наличие в каждом классе абсолютно одинаковой таблицы функций?

    ОтветитьУдалить