【问题标题】:Alternatives to passing an object over and over again一遍又一遍地传递对象的替代方法
【发布时间】:2013-03-14 19:18:22
【问题描述】:

有几个类经常被其他类派生。它们是用户输入接收器,如按键接收器、按键释放接收器、鼠标移动接收器等。继承自这些的类例如是 hotkey-action,它继承自 key down 接收器和 key release 接收器,或者一个 camera-rotator,它继承自 mouse move 接收器。用户所做的输入将由另一个类转发到相应的接收器,因此任何输入接收器,例如相机旋转器需要在该转发类中注册。转发输入的类总是相同的,永远不会有两个。

当我的类的用户创建一个新的输入接收器时,一个继承自例如的类鼠标移动接收器,鼠标移动接收器需要知道转发类以便注册自己以接收鼠标输入。所以鼠标移动接收器构造函数的代码如下:

mouseMoveReceiverIF(inputforwarder* ifo)
{
    ifo.registerInputReceiver(this);
}

由于很明显只有一个输入转发器,如果​​用户必须通过它不仅不那么直观,而且对于创建的每个输入接收器来说都是额外的输入。

但是,如果我将 inputforwarder 设置为单例或全局变量,以便输入接收器可以访问它而无需用户在构造函数中传递它,我会有其他问题:那么用户需要要知道 inputforwarder 在创建任何 inputforwarder 之前必须存在。没有什么会警告他,没有什么会迫使他首先创建输入转发器。程序会简单地崩溃,因为输入接收器会尝试向不存在的输入转发器注册。那会更糟。

一个有效和正确的方法是怎样的?上述示例的替代方法是什么?

谢谢!

【问题讨论】:

  • 你的设计过于复杂(我的意思是真的过于复杂(是的,它是 FUBAR,你必须重新开始(是的,真的))。减少继承,最好是零,看看多态函数(std::function 来自<functional> 在这里有帮助)。
  • @Zoidberg:感谢您的提示。我是一个没有经验的程序员,对你来说看起来很糟糕的设计对我来说看起来不错 - 我不知道它何时以及为什么会造成麻烦以及现在的方式,它很容易使用:用户创建一个从所需的基本输入接收器(例如 keydown 接收器)继承的输入接收器,只需要实现 onKeyDown 方法。我不确定您对使用多态函数的含义。类似函子模板的东西?并使用它向输入转发器注册任何类的方法(或任何其他函数)? (如果是这样,为什么会这样?)

标签: c++ interface singleton dependencies


【解决方案1】:

首先,我会质疑您是否应该对用户隐藏 InputForwarder 的实例。调用这些构造函数的用户代码正在配置系统并设置依赖关系——很可能代码应该明确声明这些依赖关系,即使这意味着输入额外的构造函数参数。

假设您要隐藏它,这取决于输入转发器的创建方式:

  • 如果可以不带参数创建,就让它像自启动服务一样。也就是说,您有一个返回“the”实例的函数,如果在调用该函数时该实例不存在,则该函数将创建它。

  • 如果接受用户提供的参数,则不能是自启动服务。您可以提供一个由用户设置的全局“默认输入转发器实例”,然后在构造函数中检查该实例是否存在——如果它在那里注册或引发异常。

如果您担心,可测试性在这里可能不是问题。即使你有一个全局的“默认”实例来隐藏普通用户的依赖,你仍然可以拥有这样的构造函数:

mouseMoveReceiverIF(inputforwarder* ifo = 0)
{
    if (!ifo) {
        ifo = getDefaultIfo();
        // check for null again if necessary
    }
    ifo.registerInputReceiver(this);
}

所以你可以注入任何适合测试的输入转发器。

【讨论】:

  • 谢谢!转发器是在没有参数的情况下创建的。你的提案就行了。
【解决方案2】:

您可以创建一个单例输入转发器, 并确保在程序启动时对其进行初始化。 所以现在任何客户端都无法在它创建之前访问它。

关于您的设计的另一件事: 您可能需要考虑使用装饰器模式来实现接收者的专业化。

编辑: 如果你不想要单例模式(因为单元测试等......) 比工厂方法更有效, 类似于:InputReceiverFactory.GetInputReceiver()

【讨论】:

  • 对于单例,用户仍然必须知道或不要忘记在程序启动时对其进行初始化以防止崩溃,因为如果他不这样做仍然不会发出警告。不过,工厂听起来是个不错的选择,谢谢!
  • Singleton 可以在第一次调用它的实例方法时初始化自己,即惰性初始化。这可以以简单的方式完成,也可以支持安全的跨线程调用。它也可以在加载到内存时自行初始化,即急切初始化。无论如何,客户端通常不关心初始化单例。在我看来,工厂只会更好地进行单元测试并支持松散耦合。
【解决方案3】:

2 条建议:

1) 创建一个 Init 函数/静态类成员,为您创建输入转发器。这是一种很好的封装形式。

2) 使 inputforwarder 类成为具有所有静态成员和私有构造函数的静态类。如果需要初始化任何东西,您可以使用 init 类方法来完成。

希望这会有所帮助。我听说静态类有时是一种不好的做法,但如果您不想恢复到函数,命名空间将是唯一的其他解决方案。 (据我所知)

【讨论】:

  • “使 inputforwarder 类成为一个包含所有静态成员和私有构造函数的静态类。如果需要初始化任何东西,您可以使用 init 类方法来完成。”你没有命名空间。
  • 谢谢。我认为这样就可以了。
【解决方案4】:

如果我理解您的设计,您能否通过模板将 inputforwarder 设为课程的一部分?然后你可以自动让它调用正确的转发器。

就我个人而言,我会远离单例——它们往往会使单元测试变得困难。

【讨论】:

  • 我不明白你的提议。哪个类的输入转发器部分通过模板?介意发布一个小例子吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-14
  • 2017-06-11
  • 1970-01-01
  • 2012-04-06
  • 2021-05-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多