【问题标题】:How to avoid recursion when using two Singletons?使用两个单例时如何避免递归?
【发布时间】:2013-03-14 15:16:09
【问题描述】:

我有一个 Gui 类,它是一个 Singleton,因为许多其他类必须使用 Gui 的方法,并且任何时候都应该只有一个 Gui 的实例。

我还有一个 Player 类(有点像 Audioplayer),它也是一个 Singleton。

当我启动 Gui 时,我告诉玩家获取当前状态(这会创建一个新的玩家实例)并将其显示在 Gui 上。因此,玩家创建了一个 Gui 的新实例,因为 Gui 的 Constructor 还没有完成。

因此,这会产生无限递归。 我想保留单例模式。 有没有办法将 getInstance() 中的实例设置为“null”以外的其他值,即使 Constructur 尚未完成?

谢谢

【问题讨论】:

  • 如果你仍然想使用单例,一个简单的方法是使用同步块,例如: public static Gui getInstance(){ // 同步很昂贵,因此请在外面检查。 if(instance == null){ synchronized(Gui.class){ // 在获得控制权后再次检查是否有其他线程 // 已经更新了实例。 if(instance == null){ instance = new Gui(); } } } 返回实例; }

标签: java design-patterns recursion singleton infinite


【解决方案1】:

我可能会avoid the singleton pattern altogether。正如您所发现的,它使控制创建生命周期变得困难。

相反,我会创建一个GuiManager,它会创建Gui,然后将其注入到需要了解它的适当组件中?这称为inversion of control(或dependency injection)并避免了对全局状态的需要。好处包括使测试变得容易(因为周围的框架控制着对象的生命周期)并且这些对象的创建是可预测的。

【讨论】:

  • 如果我必须在每个类的构造函数中为每个类提供一个 gui 实例,这不是坏代码吗?
  • @user1894572 不一定。为什么你会认为它不好?
  • 我希望不是每一堂课。但是(比如说)一个 Player 对象可以通过 PlayerManager 创建/管理,它可以引用一个 Gui 并管理 Player/Gui 交互。
  • 因为它有点复杂。如果有一个类,它是在另一个类中创建的,我必须给这个第一个类提供 gui,即使这个第一个类并不真的需要 gui。
  • 仅通过构造函数进行依赖注入不是强制性的。你也可以使用 setter。
【解决方案2】:

创建一个初始化函数

public class Gui
{
    public static Gui instance;
    public static Gui getInstance()
    {
        if(instance == null)
        {    
            instance = new Gui();
            instance.initialize();
        }
        return instance;        
    }

    private Gui()
    {

    }

    private initialize()
    {
        // do constructor work here
    }
}

【讨论】:

  • 这看起来很有趣,我试试看。
【解决方案3】:

我会使用静态初始化块。在构造类时调用它。不是在创建对象时。并且它只调用一次。 see more

【讨论】:

  • 似乎也很有趣...我会测试一下。谢谢
【解决方案4】:

这种递归是一个更普遍的设计问题的症状。您当然可以尝试通过从构造函数中“泄漏”实例引用或将“构造”推迟到稍后调用的某些初始化方法(可能是懒惰的)来解决它,但我不建议这样做1

为什么GUI Player 类必须是单例的并且必须相互了解,更具体地说,必须在构建过程中相互了解?除了“更方便”之外,还有其他原因可以全局访问某个对象吗?并不是说方便不重要,但它通常不应该是设计决策的唯一原因。

更好的方法是完全摆脱单例,因为没有真正的理由不能同时存在 Player 和 GUI 的两个实例。当然,您的程序中可能只有一个,但这不是单例的目的。

此外,您应该只在一个方向上具有依赖关系。试着想想什么可以没有对方而存在,什么不能。我认为 GUI 应该取决于 Player,因为您可能可以想象没有 GUI 的 Player,但是没有 Player 的 GUI 根本没有意义。因此,您首先构造一个 Player,然后构造一个 GUI,并将其操作的 Player 实例传递给它。如果您认为您需要从 Player 内部的任何地方访问您的 GUI 实例,您应该重新考虑您的设计并尝试使这些对象更加独立。这通常可以通过使用观察者模式来完成,并让 GUI 监听发生的变化/动作/事情。这样,您的应用程序代码根本不知道 GUI。但是,在您的 GUI 中,您可以随意传递 GUI 的实例。

关于引入“经理”对象或工厂模式的想法:它可能很有用,但你当然可以在没有它的情况下完成我上面所说的所有操作,并且仍然有一个理智的设计。我通常会反对“经理”课程,因为它们往往会做太多事情,并且通常是伪装的单身人士。然而,工厂可能很有用,但前提是您希望将事物的实际构造与它们的使用分离,例如允许稍后实现不同类型的 GUI。

所以一个简单的实现应该是这样的:

public static void main(...) {
    Player player = new Player();
    // maybe some other stuff to configure/set up the player

    GUI gui = new GUI(player);
    gui.show(); // or something similar
}

如果您想使用工厂,当然可以将 new ... 表达式替换为您之前必须获得的工厂的调用。

1  为什么?好吧,例如,如果您构造 GUI 成功,但后来无法初始化它,会发生什么,例如抛出异常?您需要某种方式将此“无效”状态传达给已获得 GUI 实例的所有对象。此外,您必须确保在初始化 GUI 实例之前没有人使用它。

【讨论】:

  • 这是一个非常好的主意。我真的应该避免双重依赖。我还应该使用某种“管理器”,它在一开始就被调用,它调用所有的构造函数并传递实例。我认为这是最好的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-13
  • 2021-09-20
  • 1970-01-01
  • 2013-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多