5 回答
TA贡献1796条经验 获得超4个赞
在谈论单例时,我经常试图向人们解释一个重要的观点:
单例与仅创建 1 个实例的事物之间存在差异。而且,通常,当您认为自己想要一个单例时,实际上您只是想要只创建 1 个实例的东西。
这两件事之间的区别一开始可能并不明显,但认识到这一点很重要,特别是当您发现自己需要在测试之间清除单例的内部状态时。
如果您有一个单例(真正的单例),根据定义,JVM 中可以存在一个实例。如果它具有可变状态,那么这是有问题的,因为这意味着您必须关心该状态。在测试中,您必须清除运行之间的状态,以消除由于测试执行顺序而产生的任何影响;并且您必须连续运行测试。
如果您使用依赖注入(如概念中所示,而不是像 Guice、Dagger、Spring 等任何特定框架),那么使用该实例来自的实例的类并不重要:您作为该类的客户端,得到控制其生命周期。因此,虽然您的生产代码在所有地方使用相同的实例,但您的测试代码可以使用单独的实例 - 因此它们是解耦的- 通常您甚至根本不必担心清理状态,因为您的下一个测试用例可以只需创建该类的一个新实例即可。
因此,不要像Core
这样使用类的代码:
class MyClass {
void foo() {
Core core = Core.getInstance();
// ... do stuff with the Core instance.
}
}
你可以这样写:
class MyClass {
private final Core core;
MyClass(Core core) { this.core = core; }
void foo() {
// ... do stuff with the Core instance.
}
}
MyClass并且您已经破坏了和之间的静态绑定Core。您可以MyClass在测试中实例化以下单独的实例Core:
MyClass myClass = new MyClass(new Core());
// Assert something...
或者,如果多个实例需要与同一实例交互Core:
Core core = new Core();
MyClass myClass = new MyClass(core);
MyOtherClass myOtherClass = new MyOtherClass(core);
// Assert something...
TA贡献1828条经验 获得超4个赞
您应该创建构造函数private,以便使用单例类的代码无法使用它创建实例,并且它们应该只能使用该方法获取实例getInstance()。
此外,单例对象的生命周期通常与 JVM 相关,因为每个 JVM 都应该有一个单例类的实例。因此,如果您可以销毁并重新创建实例,那么它就不是真正的单例(IMO),所以我假设您只想重新创建实例以进行测试。
要在调用该方法后从测试类重新创建单例,destroy()您可以获取Field具有您的类实例的类的 。使用它,Field您可以将其设置为您创建的新实例:
public static void main(String[] args) throws Exception {
System.out.println(Core.getInstance()); //gets instance
Core.destroy();
System.out.println(Core.getInstance()); // null
reinitializeInstance(Core.class);
System.out.println(Core.getInstance()); //gets instance
}
public static void reinitializeInstance(Class<Core> clazz) {
try {
Constructor<Core> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Core newCore = constructor.newInstance();
Field field = Core.class.getDeclaredField("instance"); //gets the instance field
field.setAccessible(true);
field.set(newCore, newCore);
} catch (Exception e) {
e.printStackTrace();
}
}
还有你的 Singleton 类:
class Core {
private static Core instance = new Core();
// To prevent reflection from creating a new instance without destroying the first one
private Core() {
if(instance != null){
throw new IllegalStateException("Instance already exists!");
}
}
public static Core getInstance() {
return instance;
}
public static void destroy() {
instance = null;
}
}
TA贡献1780条经验 获得超1个赞
public class Core {
private static class SingletonHolder {
private static AtomicReference<Core> instance = new AtomicReference(new Core());
}
public static Core getInstance() {
return SingletonHolder.instance.get();
}
public static void destroy() {
SingletonHolder.instance.set(null);
}
public static void reset() {
SingletonHolder.instance.compareAndSet(null, new Core());
}
}
使用额外的“多余”内部类来进行并发初始化,确保静态字段被初始化一次。
更改实例(销毁、重置)需要对对象进行某种同步。synchronize可以使用 AtomicReference来代替成本更高的方法。
compareAndSet如果已有旧值,则不会为实例设置新值。
也是值得拥有的
Optional<Core> getInstance() { ... }
所以使用是有保障的。
Core.getInstance().ifPresent(core -> { ... core ... });
TA贡献1809条经验 获得超8个赞
首先,您要使 Core 构造函数可访问,但默认情况下它已经是公共的。
其次,当你调用构造函数时,它只是创建一个新的 Core 实例,它对实例没有任何作用,因为默认创建的构造函数是空的,而且构造函数不是初始化 Singleton 的地方。
如果你想刷新单例实例,你应该有一个专用的方法。
TA贡献1876条经验 获得超7个赞
换成这个模式怎么样?
public class Core{
private static Core instance;
public static Core getInstance() {
if(instance == null) instance = new Core();
return instance;
}
public static void destroy(){
instance = null;
}
}
如果您只想在测试中销毁,您可以从 destroy() 方法中删除“public”
添加回答
举报