Работа над надежным шаблоном команд с shared_ptr - PullRequest
2 голосов
/ 22 апреля 2011

Я пытаюсь реализовать очень чистый шаблон команд в библиотеке.

У меня сейчас следующая структура (несколько частей все еще дорабатываются):

  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. Код клиента cannot (без обходных путей) вызов Command::execute(), только Manager может , Manager::execute(shared_ptr<Command>), так что он может обрабатывать отмену / повтор и получать неконстантные умные указатели
  6. Manager должен иметь возможность разрешать Command объектам доступ и изменять shared_ptr<Foo>, даже если пользователь инициализирует Command objecst с shared_ptr<const Foo>

Я просто пытаюсь выяснить,лучший способ справиться с выдачей shared_ptr<const Foo>, в то же время позволяя работать цифрам 5 и 6.

Есть ли какой-нибудь пример / шаблон проектирования, который делает это, из которого я мог бы извлечь уроки?Это хорошая идея по сравнению с тем, что у меня уже есть / я работаю?

Ответы [ 3 ]

2 голосов
/ 22 апреля 2011

Я думаю, это 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 ничего не знает о Manager, только оключ, необходимый для функции execute.Пользователь не сможет напрямую вызывать метод execute, поскольку только Manager может создавать CommandExecuteKey объекты благодаря частному конструктору и friend ship.

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 обратно к команде.

1 голос
/ 22 апреля 2011

Так как в противном случае для меня это не имело бы никакого смысла, я предполагаю, что

  • ваша библиотека предоставляет класс 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) получить Foo из ClientFoo и использовать его для вызова Command.

1 голос
/ 22 апреля 2011

Я не слежу за вашим вопросом на 100%, но здесь идет ...

Единственное, что я могу придумать для # 5, это сделать Command::execute частным / защищенным и сделать Manager friend из Command. Недостатком этого подхода является то, что вы теперь ввели зависимость от Command до Manager.

Что касается # 6, если shared_ptr<const Foo> объекты пользователя были получены из коллекции shared_ptr<Foo> менеджера, то Manager должен быть в состоянии безопасно const_pointer_cast shared_ptr<const Foo*> вернуться в shared_ptr<Foo*>. Если Менеджер попытается создать shared_ptr<const Foo*>, где указатель является фактическим постоянным объектом, вы получите неопределенное поведение.


Я думал о другом решении для # 5:

Определите класс ExecutableCommand, полученный из Command. ExecutableCommand имеет добавленный метод для вызова команды, который будет использоваться только Manager. Клиенты могут получить доступ к ExecutableCommand объектам только через указатели / ссылки на Command. Когда менеджер хочет вызвать Command, он понижает его до ExecutableCommand, чтобы получить доступ к интерфейсу вызова.

Рабочий пример (включая const_pointer_cast для # 6):

#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;
}
...