2

这是我长时间查看这个奇妙的网页后的第一个问题。

可能我的问题有点傻,但我想知道其他人对此的看法。更好的是,创建几个特定的​​方法,或者另一方面,只创建一个通用方法?这是一个例子......

unsigned char *Method1(CommandTypeEnum command, ParamsCommand1Struct *params)
{
if(params == NULL) return NULL;

// Construct a string (command) with those specific params (params->element1, ...)

return buffer; // buffer is a member of the class 
}

unsigned char *Method2(CommandTypeEnum command, ParamsCommand2Struct *params)
{
...
}

unsigned char *Method3(CommandTypeEnum command, ParamsCommand3Struct *params)
{
...
}
unsigned char *Method4(CommandTypeEnum command, ParamsCommand4Struct *params)
{
...
}

或者

unsigned char *Method(CommandTypeEnum command, void *params)
{
switch(command)
{
case CMD_1:
{
if(params == NULL) return NULL;

ParamsCommand1Struct *value = (ParamsCommand1Struct *) params;

// Construct a string (command) with those specific params (params->element1, ...)

return buffer;
}
break;

// ...

default:
break;
}
}

我不太喜欢后一种选择的主要是这个,

ParamsCommand1Struct *value = (ParamsCommand1Struct *) params;

因为“params”不能是指向“ParamsCommand1Struct”的指针,而是指向“ParamsCommand2Struct”或其他人的指针。

我真的很感谢你的意见!

4

7 回答 7

1

一般回答

Writing Solid Code中,Steve Macguire 的建议是针对特定情况选择不同的函数(方法)。原因是您可以断言与特定案例相关的条件,并且您可以更轻松地进行调试,因为您有更多的上下文。

一个有趣的例子是用于动态内存分配的标准 C 运行时函数。其中大部分是多余的,因为realloc实际上可以(几乎)完成您需要的一切。如果有realloc,则不需要mallocfree。但是当你有这样一个通用函数,用于几种不同类型的操作时,很难添加有用的断言,也更难编写单元测试,而且在调试时更难看到发生了什么。Macquire 更进一步并建议,不仅应该realloc只做 _re_allocation,而且它可能应该是两个不同的函数:一个用于增大块,另一个用于缩小块。

虽然我普遍同意他的逻辑,但有时使用一种通用方法(通常在操作高度数据驱动时)具有实际优势。所以我通常会根据具体情况做出决定,偏向于创建非常具体的方法,而不是过于通用的方法。

具体答案

就您而言,我认为您需要找到一种方法来从细节中提取出通用代码。这switch通常是一个信号,表明您应该使用带有虚函数的小型类层次结构。

如果您喜欢单一方法方法,那么它可能应该只是更具体方法的调度程序。换句话说,switch 语句中的每种情况都只是调用适当Method1的 ,Method2等。如果您希望用户只看到通用方法,那么您可以将特定实现设为私有方法。

于 2012-08-29T20:02:50.153 回答
1

通常,最好提供单独的函数,因为它们通过原型名称和参数直接和可见地向用户传达可用的信息;这也导致更直接的文档。

有一次我使用多用途函数是用于类似 query() 函数,其中许多次要查询函数,而不是导致函数激增,被捆绑为一个,具有通用输入和输出 void 指针.

一般来说,想想你试图通过 API 原型本身与 API 用户交流什么;清楚地了解 API 可以做什么。他不需要过多的细节;他确实需要知道核心功能,这是拥有 API 的全部意义所在。

于 2012-08-29T20:07:02.680 回答
0

首先,您需要决定使用哪种语言。用两者CC++这里标记问题是没有意义的。我假设 C++。

如果你可以创建一个通用函数,那当然更好(为什么你更喜欢多个冗余函数?)问题是;你可以吗?但是,您似乎不知道模板。我们需要查看您在此处省略的内容,以判断模板是否合适:

// 使用这些特定参数 (params->element1, ...) 构造一个字符串(命令)

在一般情况下,假设模板是合适的,所有这些都变成:

template <typename T>
unsigned char *Method(CommandTypeEnum command, T *params) {
    // more here
}

在旁注中,如何buffer声明?您是否返回指向动态分配内存的指针?更喜欢 RAII 类型的对象并避免像这样动态分配内存。

于 2012-08-29T19:39:02.757 回答
0

如果您使用的是 C++,那么我会避免使用 void*,因为您实际上并不需要。拥有多种方法并没有错。请注意,您实际上不必在第一组示例中重命名函数 - 您可以使用不同的参数重载函数,以便每种类型都有单独的函数签名。归根结底,这种问题是非常主观的,并且有多种做事方式。查看您的第一种类型的函数,您可能会通过研究模板函数的使用得到很好的服务

于 2012-08-29T19:40:21.537 回答
0

您可以创建一个结构。这就是我用来处理控制台命令的方法。

typedef int       (* pFunPrintf)(const char*,...);
typedef void      (CommandClass::*pKeyFunc)(char *,pFunPrintf);

struct KeyCommand
{
    const char * cmd;
    unsigned char cmdLen;
    pKeyFunc pfun;
    const char * Note;
    long ID;
};

#define CMD_FORMAT(a) a,(sizeof(a)-1)
static KeyCommand Commands[]=
{
    {CMD_FORMAT("one"),            &CommandClass::CommandOne,               "String Parameter",0},
    {CMD_FORMAT("two"),            &CommandClass::CommandTwo,               "String Parameter",1},
    {CMD_FORMAT("three"),          &CommandClass::CommandThree,             "String Parameter",2},
    {CMD_FORMAT("four"),           &CommandClass::CommandFour,              "String Parameter",3},

}; 

#define  AllCommands sizeof(Commands)/sizeof(KeyCommand)

和解析器功能

void CommandClass::ParseCmd( char* Argcommand )
 {
            unsigned int x;
            for ( x=0;x<AllCommands;x++)
            {
                if(!memcmp(Commands[x].cmd,Argcommand,Commands[x].cmdLen ))
                {
                    (this->*Commands[x].pfun)(&Argcommand[Commands[x].cmdLen],&::printf);
                    break;
                }
            }

            if(x==AllCommands)
            {
                // Unknown command
            }

}

我使用线程安全的 printf pPrintf,所以忽略它。

于 2012-08-29T19:41:45.623 回答
0

我真的不知道你想做什么,但在 C++ 中,你可能应该从 Formatter Base 类派生多个类,如下所示:

class Formatter
{
    virtual void Format(unsigned char* buffer, Command command) const = 0;
};

class YourClass
{
public:
    void Method(Command command, const Formatter& formatter)
    {
        formatter.Format(buffer, command);
    }

private:
    unsigned char* buffer_;
};

int main()
{
    //
    Params1Formatter formatter(/*...*/);
    YourClass yourObject;

    yourObject.Method(CommandA, formatter);
    // ...
}

这消除了处理类中所有参数的责任,并使其关闭以进行更改。如果在进一步开发过程中会有新的命令或参数,您不必修改(并最终破坏)现有代码,而是添加实现新内容的新类。

于 2012-08-29T19:52:02.117 回答
0

虽然不是完整的答案,但这应该会引导您朝着正确的方向前进:一个功能,一个责任。更喜欢只负责一件事并且做得很好的代码。需要将 void * 转换为其他类型的带有巨大 switch 语句(本身并不坏)的代码是一种气味。

顺便说一句,我希望您确实意识到,根据标准,只有当原始转换恰好是从 <type> * 转换为 void * 时,您才能从 void * 转换为 <type> *。

于 2012-08-29T20:51:57.653 回答