C++摆脱单例:函子和静态方法的替代方案

发布于 2024-11-05 13:08:31 字数 1633 浏览 0 评论 0原文

我崇高的追求是摆脱单例和静态类。

背景:
我有以下结构:

  • Cmd
    经常实例化的对象,它保存命令的名称(字符串),以及任何类的静态方法的函子作为指针。
    它通常在 main 中创建诸如输入、控制台、渲染等类,并引用在其中创建它的类中的方法,为这些方法提供运行时口头接口。
    Cmd 还以字符串数组的形式解释参数,其中首先参数是名称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("command_name", ...); 而不是 >Cmd::create("command_name",...); 这是因为 Commander 不再是静态的(或单例),因此它处理的所有命令都必须属于它。

然而,我完全不知道我的选项/替代方案是注册命令,并通过允许向指挥官发出字符串命令来保持松散耦合。

我考虑过让每个主类都派生自 CmdListener 类,该类会在创建时向 Commander 注册对象,然后在执行过程中将命令传递给所有注册的对象,从而覆盖“onCmd(const Cmd &command)” 。

这也留下了一些悬而未决的问题:Cmd 将如何转发应该调用类的哪个方法?保留指针没有意义,并且会受到高度模糊的影响(如上所述)。另外,我不希望为可能处理该 cmd 的每个类重新解释 onCmd 方法中的字符串。

这是很多信息,但是有人对如何处理这个问题有任何想法吗?

另外,我的所有类都必须了解 Commander 和 Console 对象,它们不再是单例/静态的。到目前为止,我已将它们放置在 Context 对象中,并将其像一个小胶囊一样传递。关于如何解决这些后单例残留问题有什么想法吗?

这个项目是我个人的工作,我打算在我的简历中使用它 - 因此,我不希望我的潜在雇主看到任何单身人士(我也不想解释自己为什么,因为我可以向自己证明他们并不是真正必要的)。

非常感谢!

编辑:排版。

My noble quest is to get rid of singletons and static classes.

Background:

I have the following structures:

  • Cmd
    Frequently instantiated object, it holds a name of the command (string), and functor to the static method of any class as a pointer.
    It is typically created in main classes such as Input, Console, Render, etc. and refers to methods within the class that it is created in, giving a runtime verbal interface to those methods.
    Cmds also interpret parameters in a form of a string array, where first argument is the name of the Cmd, and all consecutive strings are direct arguments for the static method being invoked. The argument count and argument array are stored in Commander, and changed before each Cmd call.
  • Commander
    Commander is used to interpret string commands (which may come directly, or through Console) and it executes the Cmd which was stored in the buffer as a string (by invoking it's functor).

Problem:

Problem is that I am attempting to get rid of all the static classes (which I now turned into singletons for testing), and I am making the system fully modular and loosely coupled. This in turn prevents me from having static calls which Cmds could point to.

First instinct was to change the functor from a typedef into a template class, which would store an object and method, but it looks very messy and complex, and I personally am not comfortable going from:

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

To:

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

The final Cmd creation looks very obscure and misleading as to who is in charge of the Functor deallocation.

I am also in the process of moving Cmd creation from a static method call, into the Commander class, so it would look like commander.createCmd("command_name", ...); instead of Cmd::create("command_name",...); This is because Commander is no longer going to be static (or singleton), so all commands which it handles must belong to it.

I am, however, at a complete loss as to what my options/alternatives are to register Cmds, and maintain the loose coupling by allowing string commands to be issued to the Commander.

I have considered making each of the main classes derive from a CmdListener class, which would register the object with the Commander upon creation, and then during execution pass a command to all registered objects which overwrote the "onCmd(const Cmd &command)".

This leaves some unanswered questions as well: how will Cmd relay which method of class should be invoked? Keeping pointers wouldn't make sense and would be subject to high level of obscurity (as demonstrated above). Also, I wish to not reinterpret strings in onCmd method for every class that may handle that cmd.

It is a lot of information, but does anybody have any ideas on how to deal with this issue?

Also, all my classes must be aware of Commander and Console objects, which are no longer singleton/static. So far, I have placed them inside a Context object, and am passing it around like a little capsule. Any ideas on how to solve these post-singleton residual problems?

This project is my personal work, and I am planning to use it on my resume - hence, I do not want my potential employers to see any singletons (nor do I want to explain myself as to why, since I can prove to myself they are not truly necessary).

Thanks a ton!

edit: typography.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

挽手叙旧 2024-11-12 13:08:31

这是 function 类的工作。您可以在 Boost、TR1 或 C++0x 中找到它。例如,它看起来像 std::function。这通常与 bind 配合使用,如果您想以通用方式引用功能对象,而不是按值获取它们,则需要它,并且也可以在升压、TR1 或 C++0x。如果你有 lambda 函数,你也可以使用它们,这是一个很好的方法。

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

This is a job for the function class. You can find one in Boost, or in TR1 or C++0x. It looks like std::function<void()>, for example. This is often partnered with bind, which you will need if you want to refer to functional objects in a generic way, rather than take them by value, and is also found in Boost, TR1 or C++0x. If you have lambda functions, you can use them too, which is an excellent method.

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();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文