创建单身人士的模式似乎是这样的:
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance()
{
return instance;
}
}
但是我的问题是,如果Singleton Constructor做了一些非单元测试友好的东西,你如何使用这样的类单元,例如调用外部服务,jndi查找等。
我认为我可以重构它:
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public synchronized static Singleton getInstance()
{
if(instance == null)
instance = new Singleton();
return instance;
}
//for the unit tests
public static void setInstance(Singleton s)
{
instancce = s;
}
}
现在的问题是,仅仅为了单元可测试性,我已经强制getInstance被同步,因此只是对于测试方面,它将对实际应用程序产生负面影响。有没有办法绕过它,似乎任何其他类型的延迟初始化都不会起作用,因为java中双重锁定模式的破坏本质。
答案 0 :(得分:11)
您可以将枚举用作Singleton
enum Singleton {
INSTANCE;
}
假设您的单身人士在单元测试中做了一些不受欢迎的事情,你可以;
// in the unit test before using the Singleton, or any other global flag.
System.setProperty("unit.testing", "true");
Singleton.INSTANCE.doSomething();
enum Singleton {
INSTANCE;
{
if(Boolean.getBoolean("unit.testing")) {
// is unit testing.
} else {
// normal operation.
}
}
}
注意:不需要同步块或显式锁定。在访问.class之前不会加载INSTANCE,并且在使用成员之前不会初始化。如果您仅使用Singleton.INSTANCE
而不是Singleton.class
,则稍后用于初始化更改的值不会出现问题。
编辑:如果您只使用Singleton.class
,则可能无法初始化该课程。它不在Java 8更新112的示例中。
public class ClassInitMain {
public static void main(String[] args) {
System.out.println("Printing a class reference");
Class clazz = Singleton.class;
System.out.println("clazz = " + clazz);
System.out.println("\nUsing an enum value");
Singleton instance = Singleton.INSTANCE;
}
static enum Singleton {
INSTANCE;
Singleton() {
System.out.println(getClass() + " initialised");
}
}
}
打印
Printing a class reference
clazz = class ClassInitMain$Singleton
Using an enum value
class ClassInitMain$Singleton initialised
答案 1 :(得分:6)
您可以使用Factory pattern创建单例,并根据环境切换实现。
或者,避免使用单例模式,而是使用Dependency Injection。
答案 2 :(得分:4)
双重检查锁定在每种语言中都被破坏,而不仅仅是Java。
我倾向于避开单身人士,但如果你需要,你可以使用持有人模式,正如Josh Bloch的 Effective Java 所推荐的那样:
public class Foo
{
static class Holder
{
static final Foo instance = new Foo();
}
public static Foo getInstance()
{
return Holder.instance;
}
private Foo()
{
}
// ...
}
编辑:修改了参考文献。
答案 3 :(得分:2)
您可以依赖注入单例实例,从单元测试代码覆盖getInstance(),使用面向方面编程来拦截方法调用并返回不同的对象,或使用像jmockit这样的工具让你嘲笑几乎所有东西,包括静力学,最终类,构造函数,以及人们通常所说的“不可测试的东西”。
我在遗留系统中采用的一种方法(我希望在对系统架构影响最小的情况下制作可测试的东西)是修改工厂方法(getInstance)以检查系统属性以寻找替代实现实例化。这被设置为单元测试套件中的备用模拟对象。
至于“双重检查锁定被破坏”语句,如果你使用volatile关键字,并且Java> = 1.5,那就不再那么真了。它在1.4及更早版本中被破坏了(即使是使用volatile),但是如果你知道你的代码只能运行在最近的JVM上,我就不用担心了。但我也不会使用单例:使用DI / IOC容器管理对象的生命周期可以更加优雅地解决您的两个问题(可测试性和同步访问器瓶颈)。
答案 4 :(得分:0)
在执行单元测试的构建阶段,你如何懒惰初始化。然后在编译分发之前将代码更改回内联初始化。
除了测试期间,您的生产代码已内联初始化。也许这种差异btw生产和测试代码可能会引入错误,但是哪个?
(当然,如果这是一个解决方案,我们让构建阶段+工具完成工作。我看到这有助于maven和dp4j)。