有必要销毁单例实例吗?

15

使用单例模式,只能创建一个实例。我们是否需要销毁该实例?

我有一个名为DBManager的单例,它管理JDBC连接和查询操作。通过调用其静态的newInstance方法,我可以获取它的实例,然后进行一些查询。最后,我想关闭数据库连接,并调用另一个静态方法close来关闭JDBC连接。

现在连接已经关闭,而DBManager的实例仍然存在但是无用。我需要通过将其赋值为null来销毁它吗?否则,它可能会被错误地引用。

如果我将该实例赋值为null,然后再次调用newInstance方法,我会得到另一个新的不同实例吗?


当然,一旦您的进程结束,您就会知道。 - BoltClock
8个回答

11

不必纠结于“单例”的语义——你的要求是DBManager最多只存在一个实例。一旦该实例变得无用,你可以将其销毁,以便在需要时创建新实例,或者定义newInstance方法(我建议将其重命名为getInstance),如果在单例被销毁后调用,则抛出异常(例如 IllegalStateException)。

如果你打算在单例被销毁时将其销毁,我建议这个操作在单例类内部自动完成,不需要外部帮助。另外,你应该考虑完全隐藏单例DBManager并实现委托模式。这样可以避免客户端保留对旧DBManager实例的引用问题。然后你可以将委托对象作为普通单例。


+1 提醒他考虑可以重新创建的情况。 - DJClayworth

5
我认为不能销毁单例,因为必须始终有一个实例可用。而且,它需要是相同的实例,否则它就不是单例(例如,两个不同的类可能持有对该类的不同实例的引用)。此外,这也是我认为单例模式在实际软件中几乎没有用处的许多原因之一。你想要确切地一个东西,在所有情况下都只有一个,甚至防止人们调用构造函数来强制执行这一点,这种情况太过死板了。这听起来像是一个曾经似乎合理的单例情况,但现在已经明显可以使用多个实例。
所以请考虑是否必须使用单例 - 你能否将其简单地作为连接的包装器,并根据需要进行连接?

1
在这种情况下,像adarshr提到的那样,为连接池机制创建一个Singleton可能是有意义的。您只会拥有一个连接池,这似乎是处理它的正确类。我同意您帖子的基本内容,事实上,在向不熟悉设计模式的小团体解释Singleton时,我遇到了许多坑,因为所有不适合或纯粹荒谬的情况都是Singleton。 - jprete
1
很多年前我曾经开发过一个应用程序,它只使用了一个数据库。之后我们想让代码能够使用两个数据库。但是该应用程序使用了单例模式,使得我们陷入了困境。 - Tom Hawtin - tackline
1
@Tom:这种情况是我们一直在纠结的事情之一。我的最终结论是,我根本不应该谈论单例模式。对我来说,这个问题,尤其是Andrzej的答案,真正揭示了单例模式是一个有用模式的想法存在漏洞。 - jprete
1
Singleton的契约规定“最多一个”。有很多情况下,Singleton的创建会被延迟到需要时再进行 - 这是使它(有时)比静态实用程序类更好的事情之一。 - DJClayworth

2

我认为更符合Singleton模式的做法是使DBManager能够在不销毁DBManager本身的情况下打开和关闭连接。然后,您将保留它,直到下一次需要数据库连接,并要求同一个DBManager对象创建新连接。毕竟,如果它是一个DBManager,则管理连接;它不代表任何单个连接。


我们可能同时在写。我投了你的反对票,因为你把类加载器牵扯进去了,这只会让人们感到困惑。 - jprete
你对我的第一条评论显示的是“38分钟前”,而这时应该是“34分钟前”。可能是同时发生的,是吧。 ;) - user unknown
我倾向于花很长时间写答案。当我在写作时,我看到了Andrzej的答案弹出,不得不验证我们是否有不同的答案。 - jprete
+1 这解释得很好,这就是为什么你同时评论了我的答案。 - user unknown

2
在这种情况下,我们使用连接池机制。每次查询数据库的操作必须打开和关闭连接。然而,由于我们使用了连接池,连接会被返回到池中而不是被物理关闭。
连接池中将有一个名为IDLE_CONNECTION_TIMEOUT或类似的设置,如果连接在配置的时间段内未被使用,则会自动过期并关闭。因此,在这种情况下,开发人员无需担心太多。

0
你的DBManager类应该处理数据库连接关闭时的清理工作。例如,如果DBManager有一个对Connection类的引用,那么你可以在newInstance()方法中编写代码来检查连接是否存活,然后返回静态引用。就像这样:
static DBManager manager;
static DBManager newInstance(){
if (manager == null) manager =new DBManager();
else if ( manager !=null && connection ==null) //if connection is closed 
manager =new DBManager();

return manager;
}

0

简短回答:不行。

详细回答:除非使用特殊的类加载器,否则无法销毁单例。如果需要销毁它,则根本不应该使用单例。也许您可以以重新打开的方式重写它 - 更好的方法是避免使用单例模式。

搜索反模式或代码异味。


构建一个可以被销毁的单例非常简单。程序员决定单例模式将被遵循到什么程度(或不遵循),类加载器与此无关。 - jprete
Singleton 意味着只有一个实例。如果你寻找这个模式,你总会发现一个涉及 Java 静态的解决方案。然后你就卡住了。TO 自己对这种情况说了什么?“通过调用它的静态 newInstance 方法...”。如果你做了其他事情,并且仍然称之为“singleton”,我不会和你争论,但如果 TO 真的有这样的想法,他也不会问这个问题,对吧? - user unknown
1
是的。并且newInstance()可以轻松地实现创建一个新实例并将其放入静态字段中。此时编写deleteInstance()方法以将旧实例置空(并进行其他清理)变得微不足道。这使得newInstance()有能力从头开始重新创建单例。你可以合理地争辩说这不再是一个单例,但类加载器并没有阻止任何人以这种方式进行操作。 - jprete
好的,这离第二种解决方案也不远了:允许打开和关闭连接,这是您成功采用的。 - user unknown

0

答案应该是不行,因为单例模式只允许存在一个实例。至于你的问题,你有一个DBManager类,它拥有一个已关闭且无用的连接。

我猜你的目标应该是在任何时候都只有一个DB连接处于打开状态,所以我建议你检查一下你的代码,是否违反了单一职责原则,并确保将连接的责任分离到一个单独的类中,让你的单例有管理其连接的特权——即根据需要关闭和重新打开连接。


1
必须有“最多一个”单例。创建通常会延迟到需要时。 - DJClayworth

-1
创建一个类变量的getter/setter,并将其设置为null以重新实例化对象 例如:
//Singleton support ...
private static A singleton = null;
    public static A get() {
        if (singleton == null){
        singleton = new A();
    }
        return singleton;
}
public static A getSingleton() {
    return singleton;
}
public static void setSingleton(A singleton) {
    A.singleton = singleton;
}

//Re instantiate 
public class Test(){
....
....
    A.setSingleton(null);

}

1
拥有setter的意义是什么?为什么不只使用一个销毁方法(简单地将单例设置为null,优点是您无法尝试将其设置为其他内容)? - Renato

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接