2008-10-27 2 views
8

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


// 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 экранов кода, содержащего много , если это и еще, если это.

ответ

14

Имейте класс для каждого идентификатора, который реализует общий интерфейс. В принципе, IIRC Strategy pattern.

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

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

+1

Я успешно использовал этот шаблон, чтобы превратить программу 1000+ в 10-строчный цикл (+1). – ThatBloke 2008-10-27 09:01:21

+3

. Вероятно, вы имеете в виду 10-строчный цикл в основной и 2000+ линиях в вспомогательных классах. необходимо выполнить работу ... – Ilya 2008-10-27 09:12:15

+0

Кроме того, вы теряете любую оптимизацию с помощью встраивания, поскольку вы работаете с вызовами виртуальных функций. Я также вижу некоторый удар производительности. – 2008-10-27 11:31:40

2

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

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

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

Dave

1

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

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

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

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

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

4

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

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

1

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

1

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

8

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

Использование нотации 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; 
} 
2

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

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

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

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

Смежные вопросы