2

これは、この素晴らしいWebページを長い間チェックした後の私の最初の質問です。

おそらく私の質問は少しばかげていますが、これについて他の人の意見を知りたいです。いくつかの特定のメソッドを作成する、または一方、1つのジェネリックメソッドのみを作成する方が良いのは何ですか?ここに例があります...

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

一般に、別の関数を提供する方が適切です。これらの関数は、プロトタイプ名と引数によって、使用可能なものをユーザーに直接かつ視覚的に伝えるためです。これは、より簡単なドキュメントにもつながります。

私が多目的関数を一度使用するのは、query() 関数のようなもので、関数の急増につながるのではなく、いくつかのマイナーなクエリ関数が 1 つにバンドルされ、一般的な入力と出力の void ポインターがあります。 .

一般に、API プロトタイプ自体によってAPI ユーザーに何を伝えようとしているのかを考えてみてください。API ができることの明確な感覚。彼は過度の詳細を必要としません。彼は、そもそも API を持つことの要点であるコア機能を知る必要があります。

于 2012-08-29T20:07:02.680 に答える
1

一般的な回答

堅実なコードを書くで、Steve Macguireのアドバイスは、特定の状況では個別の関数 (メソッド) を優先することです。その理由は、特定のケースに関連する条件をアサートでき、より多くのコンテキストがあるため、デバッグがより簡単になるからです。

興味深い例は、動的メモリ割り当てのための標準 C ランタイムの関数です。そのほとんどは冗長であり、realloc実際に必要な (ほとんど) すべてを行うことができます。がある場合は、またはreallocは必要ありません。しかし、いくつかの異なるタイプの操作に使用されるこのような一般的な関数がある場合、有用なアサーションを追加するのは難しく、単体テストを作成するのは難しく、デバッグ時に何が起こっているのかを確認するのは難しくなります。Macquire はさらに一歩進んで、単に _re_allocation を実行するだけでなく、おそらく 2 つの異なる関数 (ブロックを拡大するためのものとブロックを縮小するためのもの) であるべきだと示唆しています。mallocfreerealloc

私はおおむね彼の論理に同意しますが、1 つの汎用メソッドを使用することには実際的な利点がある場合があります (多くの場合、操作が高度にデータ駆動型である場合)。そのため、私は通常、ケースバイケースで決定し、過度に汎用的なメソッドではなく、非常に具体的なメソッドを作成する傾向があります。

具体的な答え

あなたの場合、詳細から共通コードを除外する方法を見つける必要があると思います。これswitchは、多くの場合、仮想関数を持つ小さなクラス階層を使用する必要があるというシグナルです。

単一メソッドのアプローチが好きな場合は、より具体的なメソッドへの単なるディスパッチャにする必要があります。つまり、switch ステートメントの各ケースは、適切な 、 などを呼び出すだけMethod1ですMethod2。ユーザーに汎用メソッドのみを表示させたい場合は、特定の実装をプライベート メソッドにすることができます。

于 2012-08-29T20:02:50.153 に答える
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
            }

}

スレッドセーフなprintfpPrintfを使用しているので、無視してください。

于 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

完全な答えではありませんが、これは正しい方向に導くはずです:1つの機能と1つの責任。1 つのことだけを担当し、それを適切に実行するコードを優先します。void * を他の型にキャストする必要がある巨大な switch ステートメント (それ自体は悪くない) を持つコードは臭いです。

ところで、標準によれば、元のキャストが正確に <type> * から void * である場合にのみ、void * から <type> * にキャストできることを理解していただければ幸いです。

于 2012-08-29T20:51:57.653 に答える