Как я могу улучшить этот код C ++ - PullRequest
8 голосов
/ 27 октября 2008

Я хочу получить ваше предложение по следующему псевдокоду. Пожалуйста, предложите, как я могу улучшить его, могу ли я использовать некоторые шаблоны проектирования.


// i'm receiving a string containing : id operation arguments
data    = read(socket);
tokens  = tokenize(data," "); // tokenize the string based on spaces
if(tokens[0] == "A") {
   if(tokens[1] == "some_operation") {
      // here goes code for some_operation , will use the remaining tokens as arguments for function calls
   }
   else if(tokens[1] == "some_other_operation") {
     // here goes code for some_other_operation , will use the remaining tokens
   }
   ...
   else {
     // unknown operation
   }
}
else if(tokens[0] == "B") {
   if(tokens[1] == "some_operation_for_B") {
     // do some operation for B
   }
   else if(tokens[1] == "yet_another_operation") {
     // do yet_another_operation for B
   }
   ...
   else {
     // unknown operation
   } 
}

Надеюсь, вы поняли. Дело в том, что у меня есть большое количество идентификаторов , и у каждого есть свои операции , и я думаю, что уродливо иметь 10 экранов кода, содержащих много if's и еще если .

Ответы [ 8 ]

14 голосов
/ 27 октября 2008

Есть класс для каждого идентификатора, который реализует общий интерфейс. В основном шаблон стратегии IIRC.

Итак, вы бы назвали (псевдо) код как:

StrategyFactory.GetStrategy(tokens[0]).parse(tokens[1..n])

8 голосов
/ 27 октября 2008

Сначала запишите синтаксис того, что вы поддерживаете, затем напишите код для его поддержки.

Использование нотации BNF отлично подходит для этого. И использовать библиотеку Spirit для кодовой части довольно просто.

Command := ACommand | BCommand

ACommand := 'A' AOperation
AOperation := 'some_operation' | 'some_other_operation'

BCommand := 'B' BOperation
BOperation := 'some_operation_for_B' | 'some_other_operation_for_B'

Это легко переводится как анализатор Духа. Каждое производственное правило станет однострочным, каждый конечный символ будет переведен в функцию.

#include "stdafx.h"
#include <boost/spirit/core.hpp>
#include <iostream>
#include <string>

using namespace std;
using namespace boost::spirit;

namespace {
    void    AOperation(char const*, char const*)    { cout << "AOperation\n"; }
    void    AOtherOperation(char const*, char const*)    { cout << "AOtherOperation\n"; }

    void    BOperation(char const*, char const*)    { cout << "BOperation\n"; }
    void    BOtherOperation(char const*, char const*)    { cout << "BOtherOperation\n"; }
}

struct arguments : public grammar<arguments>
{
    template <typename ScannerT>
    struct definition
    {
        definition(arguments const& /*self*/)
        {
            command
                =   acommand | bcommand;

            acommand = chlit<char>('A') 
              >> ( a_someoperation | a_someotheroperation );

            a_someoperation = str_p( "some_operation" )           [ &AOperation ];
            a_someotheroperation = str_p( "some_other_operation" )[ &AOtherOperation ];

            bcommand = chlit<char>('B') 
              >> ( b_someoperation | b_someotheroperation );

            b_someoperation = str_p( "some_operation_for_B" )           [ &BOperation ];
            b_someotheroperation = str_p( "some_other_operation_for_B" )[ &BOtherOperation ];

        }

        rule<ScannerT> command;
        rule<ScannerT> acommand, bcommand;
        rule<ScannerT> a_someoperation, a_someotheroperation;
        rule<ScannerT> b_someoperation, b_someotheroperation;

        rule<ScannerT> const&
        start() const { return command; }
    };
};

template<typename parse_info >
bool test( parse_info pi ) {
  if( pi.full ) { 
    cout << "success" << endl; 
    return true;
  } else { 
    cout << "fail" << endl; 
    return false;
  }
}

int _tmain(int argc, _TCHAR* argv[])
{

  arguments args;
  test( parse( "A some_operation", args, space_p ) );
  test( parse( "A some_other_operation", args, space_p ) );
  test( parse( "B some_operation_for_B", args, space_p ) );
  test( parse( "B some_other_operation_for_B", args, space_p ) );
  test( parse( "A some_other_operation_for_B", args, space_p ) );

    return 0;
}
4 голосов
/ 27 октября 2008

Вы можете заглянуть в «Табличные методы» (как описано в «Code Complete», 2-е издание, глава 18). Я думаю, что это , что описывает Cheery. Преимущество этого - легкая расширяемость. Вам просто нужно добавить несколько записей в таблицу. Эта таблица может быть жестко запрограммирована или даже загружена во время выполнения.

Аналогично предложению Эпаги вы также можете решить эту проблему с помощью полиморфизма, когда специализированные классы выполняют действия для различных случаев. Недостатком здесь является то, что вы должны писать новые классы в случае изменений.

2 голосов
/ 27 октября 2008

Я видел решение этой проблемы, которое хорошо работало: хэш-таблица функций.

Во время компиляции для каждой поддерживаемой операции создается Perfect Hash Function , и операция связана с вызываемой функцией (указатель функции является значением в хэше, строка команды - ключ) ,

Во время выполнения функциональность команды вызывается с помощью командной строки для поиска функции в хэш-таблице. Затем вызывается функция, передающая строку «data» по ссылке. Каждая командная функция затем анализирует оставшуюся строку в соответствии со своими правилами ... шаблон стратегии также применяется в этой точке.

Заставляет код работать как конечный автомат, который (IMHO) - самый простой способ приблизиться к сетевому коду.

2 голосов
/ 27 октября 2008

Вы хотите разделить это на несколько функций, по одной для каждого идентификатора, и по одной для каждой операции.

Рекомендация, которую я обычно использую, - это высота экрана. Если у меня не получается полностью уместить функцию на экране, я начинаю думать о том, чтобы разделить все на части. Таким образом, вам не нужно прокручивать, чтобы увидеть, куда движется функция. Как я уже сказал, это руководство, а не правило, но я считаю более практичным контролировать структуру.

Если вы затем хотите использовать ОО-подход и превратить его в кучу классов, вы можете сделать это, если увидите преимущество. Будьте внимательны ко всей сантехнике, которая идет с этим, все же. Возможно, вы захотите сделать это простым.

Dave

1 голос
/ 27 октября 2008

Похоже, mxp сказал, что подход, управляемый таблицей, соответствует этому. Если у вас есть разные параметры для ваших функций, у вас может быть столбец в таблице, в котором указано количество параметров для функции в одной строке.

1 голос
/ 27 октября 2008

вы можете использовать шаблон команды ...... каждое из ваших действий будет знать свой идентификатор и операцию и добавлять себя в список во время выполнения ... тогда вы просто найдете нужную команду и передадите в каком бы контексте он ни нуждался, и он выполнит операцию.

1 голос
/ 27 октября 2008

Создать карту функций. Тогда у вас будет такой код:

consumed_count = token_mapper[tokens[0]](tokens)
remove amount of consumed tokens according to the return value and repeat.

Хотя, в любом случае, я не понимаю ваш подход, вы собираетесь написать язык, с которым трудно работать и который не будет гибким. Подумайте об этом: небольшая разница в количестве аргументов вызывает настоящий хаос в этом языке. Поэтому вы всегда ограничены 1-3 аргументами в команде.

Я бы предпочел просто использовать некоторую комбинацию генератора лексера / парсера, но если вы хотите сделать то, что вы собираетесь делать, я бы предложил вам хотя бы разделить сначала с новой строкой, затем с пробелом, и, следовательно, иметь четкий путь чтобы увидеть, предполагалось ли дать 2 или 3 аргумента.

Важно, даже если ваш язык будет сгенерирован машиной, что если в вашем генераторе будет ошибка? Неудача рано, часто неудача.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...