如何改进这段C++代码

8

我希望你能就以下伪代码给出建议。请指出我如何改进它,是否可以使用一些设计模式。


// 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,每个id都有自己的操作,我认为在代码中出现了很多ifelse if语句,这看起来有点丑陋,需要改进。

8个回答

14

为每个ID创建一个实现共同接口的类。基本上是策略模式

因此,您将调用(伪代码):

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


1
我成功地使用了这个模式,将一个1000多行的程序转化为一个只有10行循环的程序。 - ThatBloke
3
您可能是指在主类中有10行的循环,在辅助类中有2000多行的情况 :) 没有什么魔法,您仍然需要完成工作... - Ilya
此外,由于您正在使用虚函数调用,因此放弃了任何内联优化。我可以看到一些性能损失。 - Dave Van den Eynde

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'

这很容易转化为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;
}

4

你可以查看《代码大全》第二版第18章中描述的“表驱动方法”。

我认为这就是Cheery所描述的。其中一个好处是易于扩展,你只需向表中添加一些条目即可。这个表可以硬编码或在运行时加载。

Epaga的建议类似,你也可以尝试通过多态来解决这个问题,让专门的类执行不同情况下的操作。缺点是如果有变化,你需要编写新的类。


2
您希望将其拆分为多个函数,每个ID和每个操作一个函数。
我通常使用屏幕高度作为指南。如果我不能完全将一个函数放在屏幕上,我就开始考虑拆分它们。这样你就不需要滚动来查看函数的执行情况了。正如我所说,这只是一个指南,而不是一个规则,但我发现掌握结构更实用。
如果您想采用面向对象的方法,并将其转换为一堆类,如果您看到优势,可以这样做。不过请注意所有伴随它的管道工程。您可能希望保持简单。
戴夫

我正在编写同样的内容,只是如果可以将令牌更改为整数而不是字符,您的生活会更好。但将其拆分为函数是关键。 - Ilya

2
我看到了一个解决这个问题的好方法:使用函数哈希表。
在编译时,为每个支持的操作创建一个完美哈希函数,并将该操作与要调用的函数关联起来(函数指针是哈希表中的值,命令字符串是键)。
在运行时,通过使用命令字符串在哈希表中查找函数来调用命令功能。然后,传递“数据”字符串的引用调用函数。然后,每个命令函数根据其规则解析出剩余的字符串...此时也适用策略模式。
使代码像状态机一样工作,这是(在我看来)处理网络编码最简单的方法。

1
创建一个函数映射表,然后你可以编写如下代码:
consumed_count = token_mapper[tokens[0]](tokens)
remove amount of consumed tokens according to the return value and repeat.

虽然我不太理解你的方法,但你要编写一种难以处理且不灵活的语言。想一想:在该语言中,参数数量的微小差异会导致真正的混乱。因此,每个命令总是限制为1-3个参数。

我宁愿使用一些词法分析器/解析器生成器组合,但如果你想做你要做的事情,我建议你至少先按换行符拆分,然后再按空格拆分,这样就可以清楚地看到它是否意味着提供2个或3个参数。

即使你的语言是机器生成的,这也很重要,如果你的生成器出现了错误怎么办?尽早失败,经常失败。


并非我所有的函数都具有相同数量的参数。 - Geo

1

你可以使用命令模式......每个操作都知道它的ID和操作,并在运行时将自己添加到列表中...然后你只需要查找正确的命令,传递它所需的任何上下文,它就会执行操作。


1

表驱动方法似乎很适合这种情况,正如mxp所说的那样。如果您的函数具有不同数量的参数,则可以在该行中的表格中添加一列,以指定该函数的参数数量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接