コマンドパターン設計


11

コマンドパターンのこの古い実装があります。これは、すべてのDIOperation実装を通じてContextを渡すようなものですが、後で学習と学習のプロセス(決して停止しない)で最適ではないことに気付きました。また、ここでの「訪問」は実際には合わず、混乱するだけだと思います。

また、コマンドは他のことについて何も知らず、現時点ではすべてが同じキーと値のペアを共有するため、コードのリファクタリングを考えています。どのクラスがどのKey-Valueを所有しているかを維持するのは非常に難しく、変数の重複につながる場合があります。

ユースケースの例:CommandBCommandAによって設定されるUserNameを必要とするとしましょう。CommandAはキーUserNameForCommandB = Johnを設定する必要がありますか?または、共通のUserName = John Key-Value を共有する必要がありますか?UserNameが3番目のコマンドで使用されるとどうなりますか?

この設計を改善するにはどうすればよいですか?ありがとう!

class DIParameters {
public:
   /**
    * Parameter setter.
    */
    virtual void setParameter(std::string key, std::string value) = 0;
    /**
    * Parameter getter.
    */
    virtual std::string getParameter(std::string key) const = 0;

    virtual ~DIParameters() = 0;
};

class DIOperation {
public:
    /**
     * Visit before performing execution.
     */
    virtual void visitBefore(DIParameters& visitee) = 0;
    /**
     * Perform.
     */
    virtual int perform() = 0;
    /**
     * Visit after performing execution.
     */
    virtual void visitAfter(DIParameters& visitee) = 0;

    virtual ~DIOperation() = 0;
};

3
コマンドを使用してプロパティ(名前など)を設定することはできませんでした。それは非常に依存し始めます。プロパティを設定する場合は、イベントアーキテクチャまたはオブザーバーパターンを使用してみてください。
アーヘンソン

1
1.別の訪問者を介してパラメーターを渡すのはなぜですか?performの引数としてコンテキストを渡すことの何が問題になっていますか?2.コンテキストは、コマンドの「共通」部分(例:現在のセッション/ドキュメント)用です。すべての操作固有のパラメーターは、操作のコンストラクターを介してより適切に渡されます。
クリスヴァンバエル

@KrisVanBaelそれは、私が変えようとしている紛らわしい部分です。それは実際にコンテキストている間、私は...ビジターとしてそれを渡しています
アンドレアRichiardiの

@ahendersonコマンド間のイベントを意味するのですか?Key-Valueをそこに入れますか(AndroidがParcelで行うことと同様)。CommandAがCommandBが受け入れるキーと値のペアを使用してイベントを構築するという意味では同じでしょうか?
アンドレアリチャーディ

回答:


2

コマンドパラメータの可変性について少し心配です。絶えず変化するパラメーターでコマンドを作成することは本当に必要ですか?

アプローチの問題:

perform進行中に他のスレッド/コマンドでパラメーターを変更したいですか?

あなたがしたいですvisitBeforeし、visitAfter同じのCommand異なると呼ばれるオブジェクトDIParameterのオブジェクト?

コマンドにパラメータがわからないようにしたいですか?

これは現在の設計では禁止されていません。一般的なキー値パラメータの概念にはメリットがある場合がありますが、一般的なコマンドクラスに関しては気に入らないようです。

結果の例:

Commandクラスの具体的な実現を考慮してください-のようなものですCreateUserCommand。明らかに、新しいユーザーの作成を要求するとき、コマンドにはそのユーザーの名前が必要になります。クラスCreateUserCommandDIParametersクラスがわかっている場合、どのパラメーターを設定する必要がありますか?

userNameパラメーターを設定できますか、またはusername..大文字小文字を区別せずにパラメーターを扱いますか?私は多分それだけだ...ああ待って...本当に分からないのでしょうかname

一般的なキーと値のマッピングから得られる自由を見るとわかるように、クラスを実装していない人としてクラスを使用することは不当に困難です。少なくともコマンドにいくつかの定数を提供して、そのコマンドでサポートされているキーを他の人に知らせる必要があります。

可能な異なる設計アプローチ:

  • 不変パラメーター:Parameterインスタンスを不変にすることで、異なるコマンド間でインスタンスを自由に再利用できます。
  • 特定のパラメータークラス:UserParameterユーザーが関与するコマンドに必要なパラメーターを正確に含むクラスを考えると、このAPIを使用する方がはるかに簡単です。それでもパラメーターを継承することはできますが、コマンドクラスが任意のパラメーターを取ることはもはや意味がありません-もちろん、これはAPIユーザーがどのパラメーターが正確に必要か知っていること意味します。
  • コンテキストごとに1つのコマンドインスタンス:あなたのようなものを持っているあなたのコマンドが必要な場合visitBeforevisitAfter、また、異なるパラメータでそれらを再利用しながら、あなたは異なるパラメータで呼び出さ取得の問題に開放されます。複数のメソッド呼び出しでパラメーターを同じにする必要がある場合、それらをコマンドにカプセル化して、呼び出しの間に他のパラメーターに切り替えられないようにする必要があります。

はい、visitBeforeとvisitAfterを取り除きました。基本的に、performメソッドでDIParameterインターフェイスを渡します。望ましくないDIParamtersインスタンスの問題は常に存在します。これは、インターフェイスを渡す柔軟性を選択したためです。サブクラスを作成して、DIParametersの子がいっぱいになると不変にできるというアイデアが本当に好きです。ただし、「中央機関」は依然として正しいDIParameterをコマンドに渡す必要があります。私はビジターpattern..Iが何らかの方法で制御の反転を持っていると思っ実装し始め、なぜこれが...おそらくある
アンドレアRichiardiの

0

設計原則の良いところは、遅かれ早かれ、互いに矛盾することです。

説明した状況では、各コマンドが情報を取得したり、情報を送信したりできるようなコンテキストを使用することをお勧めします(特にそれらがキーと値のペアの場合)。これはトレードオフに基づいています:別々のコマンドが互いに何らかの入力であるという理由だけで、別々のコマンドを結合したくありません。CommandBの内部では、UserNameがどのように設定されているかは気にしません。使用するためにあるだけです。CommandAで同じこと:情報を設定します。他の人がそれで何をしているのか知りたくはありません-誰でもありません。

これは、ある種の受け渡しコンテキストを意味します。私にとっては、この単純なキーと値のコンテキスト(ゲッターとセッターを備えたシンプルなBeanで、「自由形式」の要素を少し制限できる)で、ソリューションをシンプルでテスト可能にすることができる場合、特に代替案はさらに悪いコマンドごとに独自のビジネスロジックがあります。


1
ここで矛盾している原則はどれですか?
ジミー・ホッファ

明確にするために、私の問題はコンテキストまたは訪問者のパターンを選択しないことです。基本的には、Visitorと呼ばれるコンテキストパターンを使用しています:)
アンドレアリチャーディ

わかりました、おそらくあなたの正確な質問/問題を誤解したでしょう。
マーティン

0

コマンドインターフェイスがあると仮定します。

class Command {
public:
    void execute() = 0;
};

そして主題:

class Subject {
    std::string name;
public:
    void setName(const std::string& name) {this->name = name;}
}

必要なのは:

class NameObserver {
public:
    void update(const std::string& name) = 0;
};

class Subject {
    NameObserver& o;
    std::string name;
private:
    void setName(const std::string& name) {
        this->name = name;
        o.update(name);
    }
};

class CommandB: public Command, public NameObserver {
    std::string name;
public:
    void execute();
    void update(const std::string& name) {
        this->name = name;
        execute();
    }
};

NameObserver& oCommandBへの参照として設定します。これで、CommandAがサブジェクト名を変更するたびに、CommandBは正しい情報で実行できます。複数のコマンドで名前が使用されている場合は、std::list<NameObserver>


答えてくれてありがとう。このデザインの問題点は、すべてのパラメーターごとにSetter + NameObserverが必要なことです。DIParameters(コンテキスト)インスタンスを渡して通知することもできますが、やはり、CommandAをCommandBと結合しているという事実をおそらく解決することはできません。つまり、CommandAはCommandBだけが知っているべきKey-Value私が試したのは、どのコマンドがどのパラメーターを必要とし、DIParametersインスタンスでそれに応じて設定/取得するかを知る唯一の外部エンティティ(ParameterHandler)を持つことでもありました。
アンドレアリチャーディ

@Kap「この設計法の問題は、すべてのパラメーターごとにSetter + NameObserverが必要なことです」-このコンテキストのパラメーターは、私にとって少し混乱しやすいと思います。この場合、変更するフィールドごとにセッターが既にあるはずです。あなたの例から、ComamndAはサブジェクトの名前を変更するようです。セッターを介してフィールドを変更する必要があります。注:フィールドごとにオブザーバーは必要ありません。ゲッターがあり、オブジェクトをすべてのオブザーバーに渡すだけです。
アーヘンソン

0

これがプログラマーでこれを処理する正しい方法であるかどうかはわかりませんが(この場合は謝罪します)、ここですべての答えを確認した後(特に@ Frank's)。この方法でコードをリファクタリングしました。

  • DIParametersを削除しました。DIOperationの入力(不変)として個々の(汎用)オブジェクトを用意します。例:
クラスRelatedObjectTriplet {
民間:
    std :: string const m_sPrimaryObjectId;
    std :: string const m_sSecondaryObjectId;
    std :: string const m_sRelationObjectId;

    RelatedObjectTriplet&operator =(RelatedObjectTripletその他);

公衆:
    RelatedObjectTriplet(std :: string const&sPrimaryObjectId、
                         std :: string const&sSecondaryObjectId、
                         std :: string const&sRelationObjectId);

    RelatedObjectTriplet(RelatedObjectTriplet const&other);


    std :: string const&getPrimaryObjectId()const;
    std :: string const&getSecondaryObjectId()const;
    std :: string const&getRelationObjectId()const;

    〜RelatedObjectTriplet();
};
  • 次のように定義された新しいDIOperationクラス(例付き):
テンプレート<class T = void> 
クラスDIOperation {
公衆:
    virtual int perform()= 0;

    仮想T getResult()= 0;

    仮想〜DIOperation()= 0;
};

クラスCreateRelation:public DIOperation <RelatedObjectTriplet> {
民間:
    static std :: string const TYPE;

    //パラメータ(不変)
    RelatedObjectTriplet const m_sParams;

    //非表示
    CreateRelation&operator =(CreateRelation const&source);
    CreateRelation(CreateRelation const&ソース);

    //内部
    std :: string m_sNewRelationId;

公衆:
    CreateRelation(RelatedObjectTriplet const&params);

    int perform();

    RelatedObjectTriplet getResult();

    〜CreateRelation();
};
  • 次のように使用できます。
RelatedObjectTriplet triplet( "33333"、 "55555"、 "77777");
CreateRelation createRel(triplet);
createRel.perform();
const RelatedObjectTriplet res = createRel.getResult();

助けてくれてありがとう、私はここで間違いを犯していないことを願っています:)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.