几种特定方法或一种通用方法

Several specific methods or one generic method?

本文关键字:方法 一种 几种      更新时间:2023-10-16

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

也许我的问题有点傻,但我想知道其他人对此的看法。创建几个特定的方法,或者只创建一个通用方法,什么更好?下面是一个例子。。。

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"的指针,而是指向"ParameCommand2Struct"或其他人的指针。

我真的很感激你的意见!

一般答案

编写实体代码中,Steve Macguire的建议是在特定情况下更喜欢不同的函数(方法)。原因是您可以断言与特定情况相关的条件,并且您可以更容易地调试,因为您有更多的上下文。

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

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

具体答案

在您的情况下,我认为您需要找到一种方法,从细节中考虑通用代码。switch通常是一个信号,表明您应该使用具有虚拟函数的小型类层次结构。

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

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

我第一次使用多用途函数是针对query()函数,其中许多次要的查询函数(而不是导致函数的激增)被捆绑为一个函数,并带有一个通用的输入和输出void指针。

一般来说,考虑一下你试图通过API原型本身与API用户进行哪些交流;清楚地知道API能做什么。他不需要太多的细节;他确实需要知道核心功能,这些核心功能是首先拥有API的全部要点。

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

如果可以创建一个通用函数,那么这当然更可取(为什么你更喜欢多个冗余函数?)问题是;你能吗?但是,您似乎不知道模板。我们需要看看你在这里遗漏了什么,以告诉你模板是否合适:

//用那些特定的params(params->element1,…)构造一个字符串(命令)

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

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

附带说明一下,buffer是如何声明的?您是否返回一个指向动态分配内存的指针?更喜欢RAII类型的对象,如果是这样的话,就避免动态分配内存。

如果你使用C++,那么我会避免使用void*,因为你并不需要。有多个方法没有错。请注意,在第一组示例中,您实际上不必重命名函数——您可以使用不同的参数重载函数,这样每种类型都有一个单独的函数签名。归根结底,这种问题是非常主观的,有很多做事的方法。看看第一种类型的函数,看看模板化函数

的使用可能会对您大有裨益

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

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)

和Parser函数

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,所以忽略它。

我真的不知道你想做什么,但在C++中,你可能应该从这样的格式化程序基类派生多个类:

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);
    // ...
}

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

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

顺便说一句,我希望你能意识到,根据标准,你只能从虚空*到<类型>*只有当最初的演员阵容恰好来自<类型>*无效*。

相关文章: