Мне нужно ваше предложение по следующему псевдокоду. Пожалуйста, подскажите, как я могу его улучшить, могу ли я использовать некоторые шаблоны проектирования.
// 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
}
}
Надеюсь, вы поняли. Дело в том, что у меня большое количество id, и у каждого свой собственный операции, и я думаю, что это некрасиво иметь 10 экранов кода, содержащих много если и иначе если.





Создайте класс для каждого идентификатора, который реализует общий интерфейс. В основном Шаблон стратегии IIRC.
Итак, вы бы назвали (псевдо) код вроде:
StrategyFactory.GetStrategy(tokens[0]).parse(tokens[1..n])
Вы, вероятно, имеете в виду 10-строчный цикл в основной и 2000+ строк во вспомогательных классах :) Нет никакого волшебства, которое вам все еще нужно для работы ...
Кроме того, вы отказываетесь от какой-либо оптимизации посредством встраивания, поскольку вы работаете с вызовами виртуальных функций. Я также вижу некоторое снижение производительности.
Вы хотите разделить это на несколько функций, по одной для каждого идентификатора и по одной для каждой операции.
Обычно я использую высоту экрана. Если у меня нет функции, полностью умещающейся на моем экране, я начинаю думать о разделении ее на части. Таким образом, вам не нужно прокручивать, чтобы увидеть, куда идет функция. Как я уже сказал, это рекомендация, а не правило, но я считаю более практичным контролировать структуру.
Если вы затем захотите применить объектно-ориентированный подход и превратить его в группу классов, вы можете сделать это, если видите преимущество. Однако помните обо всей сантехнике, которая идет с ним. Возможно, вы захотите сделать это простым.
Дэйв
Я был в середине написания того же самого, просто небольшого дополнения, если можно будет изменить токены на целые числа вместо символов, ваша жизнь станет еще лучше. Но разделение на функции - это ключ.
Создайте карту функций. Тогда у вас будет такой код:
consumed_count = token_mapper[tokens[0]](tokens)
remove amount of consumed tokens according to the return value and repeat.
Хотя я все равно не понимаю вашего подхода, вы собираетесь написать язык, с которым трудно справиться и который негибкий. Подумайте об этом: небольшая разница в количестве аргументов вызывает настоящий хаос в этом языке. Поэтому вы всегда ограничены 1-3 аргументами на команду.
Я бы предпочел просто использовать комбинацию генератора лексера / парсера, но если вы хотите делать то, что собираетесь делать, я предлагаю вам, по крайней мере, сначала разделить с новой строкой, затем с пробелом, и, следовательно, иметь четкий способ узнать, он должен был привести 2 или 3 аргумента.
Это важно, даже если ваш язык будет сгенерирован машиной, что, если в вашем генераторе будет ошибка? Неудачи рано, неудачи часто.
Не все мои функции имеют одинаковое количество параметров.
Вы можете заглянуть в «Табличные методы» (как описано в «Завершенном коде», 2-е издание, глава 18). Думаю это что описывает Cheery. Преимущество этого - легкая расширяемость. Вам просто нужно добавить несколько записей в таблицу. Эта таблица может быть жестко запрограммирована или даже загружена во время выполнения.
Подобно Предложение эпаги, вы также можете попытаться решить эту проблему с помощью полиморфизма, заставив специализированные классы выполнять действия для разных случаев. Недостатком здесь является то, что вам придется писать новые классы в случае изменений.
вы можете использовать шаблон команды ... каждое из ваших действий будет знать свой идентификатор и операцию и добавить себя в список во время выполнения ... тогда вы просто найдете нужную команду, передайте ей любой контекст ему нужно, и он выполнит операцию.
Подход, управляемый таблицами, кажется, подходит для этого, как и сказал mxp. Если у вас разное количество параметров для ваших функций, у вас может быть столбец в таблице, который указывает количество параметров для функции в той же строке.
Сначала запишите синтаксис того, что вы поддерживаете, а затем напишите код для его поддержки.
Для этого отлично подходит использование нотации 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'
Это легко переводится в парсер Spirit. Каждое производственное правило стало бы однострочным, каждый конечный символ переводился бы в функцию.
#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;
}
Я видел решение этой проблемы, которое сработало: хеш-таблица функций.
Во время компиляции для каждой поддерживаемой операции создается Идеальная хеш-функция, и операция связана с вызываемой функцией (указатель функции - это значение в хэше, командная строка - это ключ).
Во время выполнения функции команды вызываются с помощью командной строки для поиска функции в хеш-таблице. Затем вызывается функция, передающая строку «данных» по ссылке. Затем каждая командная функция анализирует оставшуюся строку в соответствии со своими правилами ... шаблон стратегии также применяется в этой точке.
Заставляет код работать как конечный автомат, что (IMHO) является самым простым способом приблизиться к сетевому коду.
Я успешно использовал этот шаблон, чтобы превратить программу из 1000+ строк в цикл из 10 строк (+1)