4

私が試しているこの問題についてのいくつかの意見が大好きです。私はOOエクスペリエンスを改善し、C++のポリモーフィック機能を十分に活用しようとしています。基本的なコマンドパーサーのコードを書き込もうとしています。それらのコマンド構造は次のようになります。

[コマンド名][引数]

コマンド名は1語の文字列に制限されます。引数は、0からNまでの文字列のリストにすることができます。

各コマンドと引数のリストは、システム内のさまざまなソフトウェアオブジェクトに送信できます。たとえば、rtpstatisticsコマンドをrtpモジュールにマップし、userstatisticsをユーザーモジュールにマップすることができます。そんな感じ。

現在、私のCLIのエントリポイントは、コマンド文字列全体を標準文字列として提供しています。また、結果をユーザーに表示するための標準出力ストリームを提供します。

私は、パーサー関数を使用してから、ifthenelseのような取引を行うことを本当に避けたいと思っています。だから私はこのようなことを考えていました:

  1. コマンドと呼ばれる基本クラスがあります。そのコンストラクターは、文字列コマンド、stdout、および対話する必要のあるオブジェクトのインターフェースを取ります。
  2. コマンド名をそれを処理するオブジェクトに一致させるコマンドファクトリを作成します。これにより、適切なコマンドに対して適切なコマンドオブジェクトがインスタンス化されます。
  3. 個別のコマンドオブジェクトはそれぞれ、指定された引数を解析し、このコマンドに対して正しい選択を行います。

私が苦労しているのは、適切なモジュールを適切なコマンドに与える方法です。これは、テンプレート引数を使用する必要がある場所ですか?各コマンドが任意のインターフェイスを使用できるように、コマンドオブジェクトに渡すモジュールをファクトリに決定させますか?

他の意見も受け付けています。私はただ学びたいと思っており、コミュニティが私にいくつかのヒントを与えることを望んでいます:-)。

4

2 に答える 2

7

あなたが探しているのは、OOPの一般的なパターンです。 デザインパターン (Gang of Fourの本)では、これをコマンドパターンと呼んでいます。

通常、テンプレートは必要ありません。すべてが実行時に解析およびディスパッチされるため、動的ポリモーフィズム(仮想関数)がおそらくより適切な選択です。

別の答えでは、ラファエルバプティスタは基本的なデザインを提案しました。彼のデザインをより完全なものに変更する方法は次のとおりです。

コマンドオブジェクトとCommandDispatcher

コマンドは、Commandクラスのサブクラスによって処理されます。CommandDispatcherコマンドは、コマンド文字列の基本的な解析(基本的には、スペースでの分割、場合によっては引用符で囲まれた文字列の処理など)を処理するオブジェクトによってディスパッチされます。

システムはのインスタンスをに登録し、のCommandCommandDispatcherインスタンスをCommandコマンド名(std::string)に関連付けます。関連付けはstd::mapオブジェクトによって処理されますが、ハッシュテーブル(またはキーと値のペアを関連付けるための同様の構造)で置き換えることができます。

class Command
{
  public:
    virtual ~Command(void);
    virtual void execute(FILE* in, const std::vector<std::string>& args) = 0;
};

class CommandDispatcher
{
  public:
    typedef std::map<std::string, Command*> CommandMap;

    void registerCommand(const std::string& commandName, Command* command)
    {
      CommandMap::const_iterator cmdPair = registeredCommands.find(commandName);
      if (cmdPair != registeredCommands.end())
      {
        // handle error: command already registered
      }
      else
      {
        registeredCommands[commandName] = command;
      }
    }

    // possibly include isRegistered, unregisterCommand, etc.

    void run(FILE* in, const std::string& unparsedCommandLine); // parse arguments, call command
    void dispatch(FILE* in, const std::vector<std::string>& args)
    {
      if (! args.empty())
      {
        CommandMap::const_iterator cmdPair = registeredCommands.find(args[0]);
        if (cmdPair == registeredCommands.end())
        {
          // handle error: command not found
        }
        else
        {
          Command* cmd = cmdPair->second;
          cmd->execute(in, args);
        }
      }
    }


  private:
    CommandMap registeredCommands;
};

解析やその他の詳細は省略しましたが、これはコマンドパターンのかなり一般的な構造です。std::mapコマンド名とコマンドオブジェクトを関連付けるハンドルに注目してください。

コマンドの登録

この設計を利用するには、システムにコマンドを登録する必要があります。シングルトンパターンCommandDispatcherを使用して、または別の中央の場所でインスタンス化する必要があります。main

次に、コマンドオブジェクトを登録する必要があります。これを行うにはいくつかの方法があります。私が好む方法は、より詳細に制御できるため、各モジュール(関連するコマンドのセット)に独自の登録機能を提供させることです。たとえば、「ファイルIO」モジュールがある場合、次の関数がありますfileio_register_commands

void fileio_register_commands(CommandDispatcher* dispatcher)
{
  dispatcher->registerCommand( "readfile", new ReadFileCommand );
  dispatcher->registerCommand( "writefile", new WriteFileCommand );
  // etc.
}

ここReadFileCommandに、およびは、目的の動作を実装するWriteFileCommandサブクラスです。Command

fileio_register_commandsコマンドが使用可能になる前に、必ず呼び出す必要があります。

このアプローチは、動的にロードされるライブラリ(DLLまたは共有ライブラリ)で機能するように作成できます。コマンドを登録する関数が、モジュールの名前に基づいて規則的なパターンになっていることを確認してください。XXX_register_commandsここXXXで、は、たとえば小文字のモジュール名です。共有ライブラリまたはDLLをロードした後、コードはそのような関数が存在するかどうかを判断し、それを呼び出すことができます。

于 2012-06-28T19:24:34.297 に答える
2

テンプレートはやり過ぎです。コマンドインタープリターが、使用可能なオブジェクトからどのコマンドが可能かを判断するだけの何かが必要だと思います。

このCLIをサポートするクラスごとに、クラスを登録する関数と、そのクラスをトリガーするコマンド名を指定します。

class CLIObject
{
   virtual void registerCli( Cli& cli ) = 0;
   virtual bool doCommand( FILE* file, char** args ) = 0;
}

class HelloWorld : public ClIObject 
{
   void registerCli( Cli& cli ) { cli.register( this, "helloworld" ); }
   bool doCommand( FILE* file, char** args ) 
   { 
       if ( !args[0] ) return false;     
       fprintf( file, "hello world! %s", args[0] ); 
       return true; 
   }
}

これで、CLIはCLIObjectから派生したすべてのクラスをサポートできます。

于 2012-06-28T18:38:03.063 に答える