Disegno del modello di comando

11

Ho questa vecchia implementazione del pattern Command. È un po 'come passare un contesto attraverso l'implementazione DIOperation , ma mi sono reso conto più avanti, nel processo di apprendimento e apprendimento (che non si ferma mai), che non è ottimale. Penso anche che la "visita" qui non sia davvero adatta e confonda semplicemente.

In realtà sto pensando di ridefinire il mio codice, anche perché un Comando non dovrebbe sapere nulla degli altri e al momento condividono tutte le stesse coppie chiave-valore. È davvero difficile mantenere quale classe possiede quale valore-chiave, a volte portando a variabili duplicate.

Un esempio di caso d'uso: diciamo che CommandB richiede UserName che è impostato da < em> Commanda . CommandA dovrebbe impostare la chiave UserNameForCommandB = John ? O dovrebbero condividere un comune valore chiave UserName = John ? Cosa succede se UserName viene utilizzato da un terzo comando?

Come posso migliorare questo design? Grazie!

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;
};
    
posta Andrea Richiardi 04.03.2013 - 18:31
fonte

4 risposte

2

Sono un po 'preoccupato per la mutevolezza dei parametri di comando. È davvero necessario creare un comando con parametri che cambiano costantemente?

Problemi con il tuo approccio:

Vuoi altri thread / comandi per modificare i tuoi parametri mentre perform sta succedendo?

Vuoi visitBefore e visitAfter dello stesso oggetto Command da chiamare con diversi oggetti DIParameter ?

Vuoi che qualcuno fornisca i parametri ai tuoi comandi, di cui i comandi non hanno idea?

Nessuno di questi è proibito dal tuo attuale design. Mentre un concetto di parametro chiave-valore generico ha i suoi meriti a volte, non mi piace rispetto ad una classe di comando generica.

Esempio di conseguenze:

Considera una realizzazione concreta della tua classe Command - qualcosa come CreateUserCommand . Ovviamente, quando richiedi la creazione di un nuovo utente, il comando avrà bisogno di un nome per quell'utente. Dato che conosco le classi CreateUserCommand e DIParameters , quale parametro dovrei impostare?

Potrei impostare il parametro userName , o username .. trattate i parametri senza distinzione tra maiuscole e minuscole? Non lo saprei davvero .. oh aspetta .. forse è solo name ?

Come puoi vedere, la libertà che ottieni da una mappatura di valori-chiave generica implica che usare le tue classi come qualcuno che non le ha implementate è ingiustificatamente difficile. Dovresti almeno fornire alcune costanti per i tuoi comandi per far sapere agli altri quali chiavi sono supportate da quel comando.

Possibili approcci diversi alla progettazione:

  • Parametri immutabili: trasformando le tue istanze di Parameter immutabili puoi riutilizzarle liberamente tra diversi comandi.
  • Classi di parametri specifici: data una classe UserParameter che contiene esattamente i parametri che avrei bisogno di comandi che coinvolgono un utente, sarebbe molto più semplice lavorare con questa API. Si può ancora avere ereditarietà dei parametri, ma non avrebbe più senso per le classi di comando assumere parametri arbitrari - dal punto di vista del pro, naturalmente, ciò significa che gli utenti API sanno esattamente quali parametri sono richiesti.
  • Un'istanza di comando per contesto: se hai bisogno che i tuoi comandi abbiano cose come visitBefore e visitAfter , mentre li riutilizzi con parametri diversi, sarai aperto al problema di ottenere chiamate con parametri diversi. Se i parametri devono essere uguali su più chiamate di metodo, è necessario incapsularli nel comando in modo tale che non possano essere disattivati per altri parametri tra le chiamate.
risposta data 05.03.2013 - 07:47
fonte
0

Ciò che è bello dei principi di progettazione è che, prima o poi, entrano in conflitto tra loro.

Nella situazione descritta, penso che preferirei andare con un tipo di contesto in cui ogni comando può ricevere informazioni e inserire informazioni (specialmente se si tratta di coppie chiave-valore). Questo si basa su un compromesso: non voglio che comandi separati siano accoppiati solo perché sono una sorta di input reciproco. All'interno di CommandB, non mi interessa come è stato impostato UserName - solo che è lì per me da usare. La stessa cosa in CommandA: ho impostato le informazioni, non voglio sapere cosa gli altri stanno facendo con esso - né chi sono.

Questo significa una sorta di contesto di passaggio, che puoi trovare male. Per me, l'alternativa è peggiore, soprattutto se questo semplice contesto-valore-chiave (può essere un semplice bean con getter e setter, per limitare un po 'il fattore "free form") può consentire alla soluzione di essere semplice e verificabile, con comandi separati, ciascuno con la propria logica di business.

    
risposta data 04.03.2013 - 19:06
fonte
0

Supponiamo che tu abbia un'interfaccia di comando:

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

E un argomento:

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

Quello che ti serve è:

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();
    }
};

Imposta NameObserver& o come riferimento a CommandB. Ora ogni volta che CommandA cambia il nome degli oggetti, CommandB può eseguire con le informazioni corrette. Se il nome è usato da più comandi usa std::list<NameObserver>

    
risposta data 04.03.2013 - 21:24
fonte
0

Non so se questo è il modo giusto per gestirlo sui programmatori (nel qual caso mi scuso), ma, dopo aver controllato tutte le risposte qui (in particolare su @ Frank). Ho rifattorizzato il mio codice in questo modo:

  • Dropped DIParameters. Avrò singoli oggetti (generici) come input di DIOperation (immutabile). Esempio:
class RelatedObjectTriplet {
private:
    std::string const m_sPrimaryObjectId;
    std::string const m_sSecondaryObjectId;
    std::string const m_sRelationObjectId;

    RelatedObjectTriplet& operator=(RelatedObjectTriplet other);

public:
    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();
};
  • Nuova classe DIOperation (con esempio) definita come:
template <class T = void> 
class DIOperation {
public:
    virtual int perform() = 0;

    virtual T getResult() = 0;

    virtual ~DIOperation() = 0;
};

class CreateRelation : public DIOperation<RelatedObjectTriplet> {
private:
    static std::string const TYPE;

    // Params (immutable)
    RelatedObjectTriplet const m_sParams;

    // Hidden
    CreateRelation & operator=(CreateRelation const& source);
    CreateRelation (CreateRelation const& source);

    // Internal
    std::string m_sNewRelationId;

public:
    CreateRelation(RelatedObjectTriplet const& params);

    int perform();

    RelatedObjectTriplet getResult();

    ~CreateRelation();
};
  • Può essere usato in questo modo:
RelatedObjectTriplet triplet("33333", "55555", "77777");
CreateRelation createRel(triplet);
createRel.perform();
const RelatedObjectTriplet res = createRel.getResult();

Grazie per l'aiuto e spero di non aver commesso errori qui:)

    
risposta data 07.03.2013 - 20:34
fonte

Leggi altre domande sui tag