Дизайн шаблон команди


11

У мене є ця стара реалізація шаблону Command. Це свого роду передача контексту через всю реалізацію DIOperation , але я зрозумів, що пізніше, в процесі навчання та навчання (що ніколи не припиняється), це не є оптимальним. Я також думаю, що "відвідування" тут насправді не підходить і просто плутає.

Я насправді думаю про рефакторинг свого коду ще й тому, що Команда нічого не повинна знати про інших, і в даний момент всі вони мають однакові пари ключ-значення. Дійсно важко підтримувати, якому класу належить який ключ-значення, іноді призводить до дублювання змінних.

Приклад варіанту використання: припустимо CommandB вимагає UserName , який встановлюється CommandA . Чи повинен CommandA встановити ключ UserNameForCommandB = John ? Або вони повинні мати спільне UserName = Ідентифікатор ключових значень? Що робити, якщо UserName використовується третьою командою?

Як я можу вдосконалити цей дизайн? Дякую!

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
Мені ніколи не пощастило використовувати команду для встановлення властивостей (наприклад, ім'я). Вона починає ставати дуже залежною. Якщо ваші параметри налаштувань, спробуйте скористатися архітектурою подій або шаблоном спостерігача.
ahenderson

1
1. Навіщо передавати параметри через окремого відвідувача? Що не так у передачі контексту як аргументу виконання? 2. Контекст для 'загальної' частини команди (наприклад, поточний сеанс / документ). Усі параметри, що стосуються конкретної операції, краще передавати через конструктор операції.
Kris Van Bael

@KrisVanBael - це заплутана частина, яку я намагаюся змінити. Я передаю це як Відвідувач, але насправді це Контекст ...
Андреа Річіарді

@ahenderson Ви маєте на увазі події між моїми командами? Ви б розмістили там свої ключові значення (подібно до того, що Android робить із Parcel)? Чи було б те саме в тому сенсі, що CommandA повинен будувати подію з пар ключа-значення, які приймає CommandB?
Андреа Річіарді

Відповіді:


2

Мене трохи хвилює змінність ваших командних параметрів. Чи справді потрібно створити команду з постійно змінюються параметрами?

Проблеми з вашим підходом:

Чи хочете, щоб інші потоки / команди змінювали ваші параметри під час роботи perform?

Ви хочете, щоб один visitBeforeі visitAfterтой же Commandоб'єкт називався різними DIParameterоб'єктами?

Ви хочете, щоб хтось подав параметри вашим командам, про які команди не мають уявлення?

Ніщо з цього не заборонено вашим поточним дизайном. Хоча поняття загального ключа-значення часом має свої достоїнства, мені це не подобається стосовно загального класу команд.

Приклад наслідків:

Подумайте про конкретну реалізацію свого Commandкласу - щось подібне CreateUserCommand. Очевидно, що коли ви вимагаєте створити нового користувача, команді знадобиться ім'я цього користувача. З огляду на те, що я знаю CreateUserCommandі DIParametersкласи, який параметр я повинен встановити?

Я можу встановити userNameпараметр, або username.. Ви ставитесь до випадку параметрів нечутливо? Я б не знав насправді .. о, чекайте .. можливо, це просто name?

Як ви можете бачити свободу, яку ви отримуєте від загального відображення ключових значень, означає, що використовувати свої класи як особу, яка не реалізувала їх, необґрунтовано складно. Вам, принаймні, потрібно надати деякі константи для своїх команд, щоб інші могли знати, які клавіші підтримуються цією командою.

Можливі різні дизайнерські підходи:

  • Незмінні параметри: Перетворюючи ваші Parameterекземпляри незмінні, ви можете вільно використовувати їх серед різних команд.
  • Конкретні класи параметрів: Враховуючи UserParameterклас, який містить саме ті параметри, які мені знадобляться для команд, що включають користувача, було б набагато простіше працювати з цим API. Ви все ще можете мати спадщину за параметрами, але більше не буде сенсу для командних класів приймати довільні параметри - з іншого боку, це означає, що користувачі API точно знають, які саме параметри потрібні.
  • Один екземпляр команди за контекстом: Якщо вам потрібні ваші команди, щоб вони мали такі речі, visitBeforeі visitAfterпри цьому повторно використовуєте їх з різними параметрами, ви будете відкриті до проблеми виклику з різними параметрами. Якщо параметри повинні бути однаковими для декількох викликів методів, вам потрібно інкапсулювати їх у команду, щоб вони не могли бути вимкнені для інших параметрів між викликами.

Так, я позбувся відвідування перед і відвідуванням. Я в основному передаю мій інтерфейс DIParameter в методі виконання. Проблема з небажаними екземплярами DIParamters завжди буде там, тому що я вирішив забезпечити гнучкість передачі інтерфейсу. Мені дуже подобається ідея мати можливість підкласи і зробити дітей DIParameters незмінними після їх заповнення. Однак, "центральному органу" все-таки потрібно передати правильний DIP-параметр Команді. Можливо, тому я почав реалізовувати шаблон відвідувачів. Мені хотілося якось інвертувати контроль ...
Андреа Річіарді

0

Принципи дизайну приємно, що вони рано чи пізно конфліктують між собою.

В описуваній ситуації я думаю, що я вважаю за краще перейти з якимось контекстом, з якого кожна команда може брати інформацію та передавати інформацію (особливо якщо це пари ключ-значення). Це ґрунтується на компромісі: я не хочу, щоб окремі команди поєднувалися лише тому, що вони є певним вкладом один до одного. Всередині CommandB мені байдуже, як було встановлено UserName - просто для того, щоб він міг користуватися ним. Те саме в CommandA: я вкладаю інформацію, я не хочу знати, що з цим роблять інші - ні хто вони.

Це означає свого роду прохідний контекст, який можна вважати поганим. Для мене альтернатива гірша, особливо якщо цей простий контекст ключових значень (може бути простим бобом з геттерами та сеттерами, щоб трохи обмежити коефіцієнт "вільної форми") може дозволити рішення бути простим і перевіреним, добре окремі команди, кожна зі своєю логікою бізнесу.


1
З якими принципами ви тут конфліктуєте?
Джиммі Хоффа

Для уточнення моя проблема полягає не у виборі між контекстом або шаблоном відвідувачів. Я в основному використовую шаблон контексту під назвою Відвідувач :)
Андреа Річіарді

Гаразд, я, мабуть, неправильно зрозумів ваше точне питання / проблему.
Мартін

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& oяк посилання на CommandB. Тепер, коли CommandA змінює ім'я суб'єктів, CommandB може виконати правильну інформацію. Якщо ім'я використовується більше команд, використовуйте astd::list<NameObserver>


Дякую за відповідь. Проблема цього дизайну imho полягає в тому, що нам потрібен Setter + NameObserver для кожного параметра. Я міг би передати екземпляр DIParameters (контекст) та повідомити, але, знову ж таки, я, мабуть, не вирішуватиму факт, що я все ще зв’язую CommandA з CommandB, тобто CommandA має поставити значення ключа, про яке повинен знати лише CommandB ... Я намагався також мати зовнішню сутність (ParameterHandler), яка єдина, яка знає, якій команді потрібен який параметр і відповідно встановлює / отримує в екземплярі DIParameters.
Андреа Річіарді

@Kap "Проблема з цим дизайном imho полягає в тому, що нам потрібен Setter + NameObserver на кожен параметр" - параметр у цьому контексті для мене трохи заплутаний, я думаю, ви мали на увазі поле. У цьому випадку ви вже повинні мати сетер для кожного поля, яке змінюється. З вашого прикладу, схоже, ComamndA змінює назву Subject. Він повинен змінити поле через сетер. Примітка: вам не потрібен спостерігач за кожним полем, просто отримайте геттер і передайте об'єкт усім спостерігачам.
ahenderson

0

Я не знаю, чи це правильний спосіб вирішити це з Програмістами (в такому випадку я вибачаюся), але, перевіривши всі відповіді тут (зокрема у Франка). Я відновив свій код таким чином:

  • Скинуті ДІПараметри. Я буду мати окремі (загальні) об'єкти як вхід DIOperation (незмінний). Приклад:
class RelatedObjectTriplet {
приватний:
    std :: string const m_sPrimaryObjectId;
    std :: string const m_sSecondaryObjectId;
    std :: string const m_sRelationObjectId;

    RelatedObjectTriplet & operator = (інші пов'язані об'єктиTriplet);

загальнодоступний:
    RelatedObjectTriplet (std :: string const & sPrimaryObjectId,
                         std :: string const & sSecondaryObjectId,
                         std :: string const & sRelationObjectId);

    RelatedObjectTriplet (ConstObjectTriplet const та інші);


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

    ~ RelatedObjectTriplet ();
};
  • Новий клас DIOperation (з прикладом), визначений як:
шаблон <клас T = void> 
клас DIOperation {
загальнодоступний:
    virtual int perform () = 0;

    віртуальний T getResult () = 0;

    віртуальний ~ DIOperation () = 0;
};

клас CreateRelation: public DIOperation <RelatedObjectTriplet> {
приватний:
    статичний std :: string const ТИП;

    // Парами (незмінні)
    RelatedObjectTriplet const m_sParams;

    // Прихований
    CreateRelation & operator = (Const CreateRest & source);
    CreateRelation (const і джерело CreateRelation);

    // Внутрішня
    std :: рядок m_sNewRelationId;

загальнодоступний:
    CreateRelation (ConstOrjectTriplet const & params);

    int perform ();

    RelatedObjectTriplet getResult ();

    ~ CreateRelation ();
};
  • Його можна використовувати так:
RelatedObjectTriplet триплет ("33333", "55555", "77777");
CreateRelation createRel (триплет);
createRel.perform ();
const RelatedObjectTriplet res = createRel.getResult ();

Дякую за допомогу, і я сподіваюся, що тут не помилився :)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.