C ++ избавляется от синглетонов: альтернатива функторам и статическим методам - PullRequest
4 голосов
/ 08 мая 2011

Мой благородный квест - избавиться от синглетонов и статических классов.

Фон:
У меня есть следующие структуры:

  • Cmd
    Часто создаваемый объект, он содержит имя команды (строка) и функтор статического метода любого класса в качестве указателя.
    Обычно создается в основных классах, таких как Input, Console, Renderи т. д. и относится к методам внутри класса, в котором он создается, предоставляя вербальный интерфейс времени выполнения для этих методов.
    Cmds также интерпретирует параметры в виде массива строк, где первый аргумент - это имя Cmd,и все последовательные строки являются прямыми аргументами для вызываемого статического метода.Количество аргументов и массив аргументов хранятся в Commander и изменяются перед каждым вызовом Cmd.
  • Commander
    Commander используется для интерпретации строковых команд (которые могут поступать напрямую или через консоль), и он выполняет Cmdкоторый был сохранен в буфере как строка (вызывая его функтор).

Проблема:
Проблема в том, что я пытаюсь избавиться от всех статических классов(который я теперь превратил в синглтоны для тестирования), и я делаю систему полностью модульной и слабо связанной.Это, в свою очередь, препятствует тому, чтобы у меня были статические вызовы, на которые мог бы указывать Cmds.

Первым инстинктом было изменение функтора из typedef в класс шаблона, который будет хранить объект и метод, но это выглядит очень грязно исложный, и мне лично неудобно переходить от:

Cmd::create("toggleconsole", Console::toggle);

к:

Cmd::create("toggleconsole", new FunctorObject<Console>(&Console::get(), &Console::toggle));

Окончательное создание Cmd выглядит очень неясным и вводящим в заблуждение относительно того, кто отвечает за освобождение Functor.

Я также нахожусь в процессе переноса создания Cmd из вызова статического метода в класс Commander, поэтому он будет выглядеть как commander.createCmd ("имя_команды", ...); вместо Cmd :: create ("имя_команды", ...); Это потому, что Commander больше не будет статичным (или синглтоном), поэтому все команды, которые он обрабатывает, должны принадлежать ему.

Я, однако, в полной растерянности относительно того, каковы мои варианты / альтернативы, чтобы зарегистрировать Cmds и поддерживать слабую связь, позволяя командным командам выдавать строковые командыmmander.

Я рассмотрел вопрос о том, чтобы сделать каждый из основных классов производным от класса CmdListener, который регистрировал бы объект с помощью Commander при создании, а затем во время выполнения передавал команду всем зарегистрированным объектам, которые перезаписали "onCmd".(const Cmd & command) ".

Это также оставляет некоторые вопросы без ответа: как Cmd будет передавать, какой метод класса должен быть вызван?Хранение указателей не имело бы смысла и было бы подвержено высокому уровню неясности (как показано выше).Кроме того, я не хочу переинтерпретировать строки в методе onCmd для каждого класса, который может обрабатывать этот cmd.

Это много информации, но есть ли у кого-нибудь идеи о том, как иметь дело сэта проблема?

Кроме того, все мои классы должны знать объекты Commander и Console, которые больше не являются одноэлементными / статическими.До сих пор я поместил их в объект Context и передаю его как маленькую капсулу.Любые идеи о том, как решить эти остаточные проблемы после синглтона?

Этот проект - моя личная работа, и я планирую использовать его в своем резюме - следовательно, я не хочу, чтобы мои потенциальные работодатели видели какие-либо одиночные игры(и при этом я не хочу объяснять себе, почему, поскольку я могу доказать себе, что они действительно не нужны).

Спасибо за тонну!

edit: типографика.

1 Ответ

5 голосов
/ 08 мая 2011

Это работа для класса function.Вы можете найти его в Boost или в TR1 или C ++ 0x.Это выглядит как std::function<void()>, например.Это часто связано с bind, который вам понадобится, если вы хотите ссылаться на на функциональные объекты общим способом, а не принимать их по значению, и также встречается в Boost, TR1 или C ++ 0x.Если у вас есть лямбда-функции, вы также можете использовать их, что является отличным методом.

class Commander {
    std::map<std::string, std::function<void()>> commands;
public:
    void RegisterCommand(std::string name, std::function<void()> cmd) {
        commands[name] = cmd;
    }
    void CallCommand(std::string name) {
        commands[name]();
    }
};
void sampleFunc() {
    std::cout << "sampleFunc()" << std::endl;
}
struct sampleStruct {
    int i;
    void operator()() {
        std::cout << i;
        std::cout << "sampleStruct()() and the value of i is " << i << std::endl;
    }
};

int main() {
    Commander c;
    c.RegisterCommand("sampleFunc", sampleFunc);
    sampleStruct instance;
    instance.i = 5;
    c.RegisterCommand("sampleStruct", instance);
    std::string command;
    while(std::cin >> command && command != "exit") {
        c.CallCommand(command);
    }
    std::cin.get();
}
...