2

ライブラリに非常にクリーンなコマンド パターンを実装しようとしています。

私は現在、次の構造を持っています(いくつかの部分はまだ仕上げ中です):

  1. ユーザー (クライアントコード) にはいくつかのオブジェクトがあり、それを「マネージャー」と呼びます
  2. Managerのコレクションを保持していますshared_ptr<Foo>
  3. Managerを返すことでコレクションへのアクセスを提供しますshared_ptr<Foo>
  4. アクションを実行するためのCommand抽象クラスとコマンドの階層がありますFoo
  5. クライアント コードは、元に戻す/やり直しを処理できるように、のみ呼び出してはなりません。Command::execute()Manager Manager::execute(shared_ptr<Command>)

以下のルールを守りたいと思います。

  1. ユーザー (クライアントコード) にはいくつかのオブジェクトがあり、それを「マネージャー」と呼びます
  2. Managerのコレクションを保持していますshared_ptr<Foo>
  3. Managerを返すことでコレクションへのアクセスを提供しますshared_ptr<const Foo>
  4. アクションを実行するためのCommand抽象クラスとコマンドの階層がありますFoo
  5. クライアント コードは (回避策なしで)を呼び出すことできませんCommand::execute()Manager Manager::execute(shared_ptr<Command>)
  6. ユーザーがobjecstをManager_Commandshared_ptr<Foo>Commandshared_ptr<const Foo>

shared_ptr<const Foo>5番と6番を機能させながら、配布を処理する最良の方法を見つけようとしています。

私が学ぶことができるこれを行う例/設計パターンはありますか? これは、私がすでに持っている/取り組んでいるものと比較して良い考えですか?

4

3 に答える 3

2

passkey patternこれはあなたにとって正しいことだと思います:

class CommandExecuteKey{
private:
  friend class Manager;
  CommandExecuteKey(){}
};

class Command{
public:
  // usual stuff
  void execute(CommandExecuteKey);
};

class Manager{
public
  void execute(Command& cmd){
    // do whatever you need to do
    cmd.execute(CommandExecuteKey());
  }
};

現在、関数に必要なキーについてのみ、 についてはCommand何も知りません。プライベート コンストラクターとシップのおかげで、オブジェクトのみを作成できるため、ユーザーはメソッドを直接呼び出すことはできません。ManagerexecuteexecuteManagerCommandExecuteKeyfriend

int main(){
  Command cmd;
  Manager mgr;
  //cmd.execute(CommandExecuteKey()); // error: constructor not accessible
  mgr.execute(cmd); // works fine, Manager can create a key
}

6 番目のポイント:
コマンドを取得したら、すべてのshared_ptr<Foo>s で正しいオブジェクトを検索し (コマンドの保存済みshared_ptrを検索キーとして使用)、内部shared_ptrの s から変更可能なオブジェクトをコマンドに戻します。

于 2011-04-22T18:42:57.333 に答える
1

私はあなたの質問に 100% 従っているわけではありませんが、ここでは...

#5について私が考えることができる唯一のことは、Command::executeプライベート/プロテクトを作成し、のを作成ManagerすることfriendですCommand. このアプローチの欠点は、依存関係を からCommandに導入したことManagerです。

#6 に関しては、ユーザーのshared_ptr<const Foo>オブジェクトが Manager のshared_ptr<Foo>コレクションから生成されたものである場合、 const_pointer_cast を安全に に戻すことができるはずManager です。Managerが、参照先が実際の定数オブジェクトであるa を const キャストしようとすると、未定義の動作が発生します。shared_ptr<const Foo*>shared_ptr<Foo*>shared_ptr<const Foo*>


#5の別の解決策を考えました:

ExecutableCommandから派生したクラスを定義しCommandます。ExecutableCommandによってのみ使用される、コマンドを呼び出すメソッドが追加されていManagerます。クライアントは、へExecutableCommandのポインタ/参照を介してのみオブジェクトにアクセスできますCommand。Manager が を呼び出したい場合、Commandそれを にダウンキャストしてExecutableCommand、呼び出しインターフェースへのアクセスを取得します。

作業例 (#6 の const_pointer_cast を含む):

#include <iostream>
#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>

using namespace std;
using namespace boost;

//------------------------------------------------------------------------------
struct Foo
{
    Foo(int x) : x(x) {}
    void print() {++x; cout << "x = " << x << "\n";} // non-const
    int x;
};

//------------------------------------------------------------------------------
struct Command
{
    // Interface accessible to users
    std::string name;

private:
    virtual void execute() = 0;
};

//------------------------------------------------------------------------------
struct ExecutableCommand : public Command
{
    // Only accessible to Manager
    virtual void execute() {} // You may want to make this pure virtual
};

//------------------------------------------------------------------------------
struct PrintCommand : public ExecutableCommand
{
    PrintCommand(shared_ptr<const Foo> foo)
        : foo_( const_pointer_cast<Foo>(foo) ) {}

    void execute() {foo_->print();}

private:
    shared_ptr<Foo> foo_;
};

//------------------------------------------------------------------------------
struct Manager
{
    void execute(Command& command)
    {
        ExecutableCommand& ecmd = dynamic_cast<ExecutableCommand&>(command);
        ecmd.execute();
    }

    void addFoo(shared_ptr<Foo> foo) {fooVec.push_back(foo);}

    shared_ptr<const Foo> getFoo(size_t index) {return fooVec.at(index);}

private:
    std::vector< shared_ptr<Foo> > fooVec;
};

//------------------------------------------------------------------------------
int main()
{
    Manager mgr;
    mgr.addFoo( shared_ptr<Foo>(new Foo(41)) );

    Command* print = new PrintCommand(mgr.getFoo(0));
    // print.execute() // Not allowed
    mgr.execute(*print);

    delete print;
}
于 2011-04-22T18:10:39.293 に答える
1

そうしないと意味が分からないので、以下のように仮定します。

  • ライブラリがManagerクラス (または少なくとも基本クラス) を提供し、
  • クライアントは、そのクラスを使用してCommand.

その場合、おそらく次のようなものが機能する可能性があります。

void Manager::execute(Command& cmd, shared_ptr<Foo const> const& target)
{
    shared_ptr<Foo> mutableTarget = this->LookupMutableFoo(mutableTarget); // throws if not found
    cmd.execute(mutableTarget); // client cannot invoke without mutable pointer
}

// or, if the "target" needs to be stored in the Command you could use something like this:
void Manager::execute(Command& cmd)
{
    shared_ptr<Foo> mutableTarget = this->LookupMutableFoo(cmd.GetTarget()); // throws if not found
    cmd.execute(mutableTarget); // client cannot invoke without mutable pointer
}

constここで使用することが最善の解決策であるかどうかはわかりません。Fooたぶん、オブジェクトをたとえばオブジェクトでラップする必要がありClientFooます。マネージャーは へのポインターのみを配布しClientFooます。その後、マネージャは (たとえば 経由でfriend) から を取得しFooClientFooそれを使用して を呼び出すことができCommandます。

于 2011-04-22T18:31:22.633 に答える