Java 如何实现近单例?
我有时需要在应用程序的生命周期中只实例化一次的类。使它们成为单例是不好的,因为这样单元测试就成了问题 但是,由于在我的应用程序的生命周期中应该只有一个这样的对象的实例,所以在应用程序运行时两次实例化这样的对象是错误的 因此,我希望我的应用程序在其生命周期中检测到这样一个对象被实例化了两次,而在单元测试时仍然允许对这样一个对象进行多次实例化时,立即抛出一个异常 我认为这不是一个不合理的要求:如果在应用程序的一个生命周期中只应该创建一个这样的对象,那么抛出异常似乎是正确的做法 以下是我正在做的:Java 如何实现近单例?,java,unit-testing,design-patterns,singleton,Java,Unit Testing,Design Patterns,Singleton,我有时需要在应用程序的生命周期中只实例化一次的类。使它们成为单例是不好的,因为这样单元测试就成了问题 但是,由于在我的应用程序的生命周期中应该只有一个这样的对象的实例,所以在应用程序运行时两次实例化这样的对象是错误的 因此,我希望我的应用程序在其生命周期中检测到这样一个对象被实例化了两次,而在单元测试时仍然允许对这样一个对象进行多次实例化时,立即抛出一个异常 我认为这不是一个不合理的要求:如果在应用程序的一个生命周期中只应该创建一个这样的对象,那么抛出异常似乎是正确的做法 以下是我正在做的: /
/**
* The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
* "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
* document are to be interpreted as described in RFC 2119.
*
* You MUST NOT instantiate this class more than once during the application's
* lifecycle. If you try to do so, an exception SHALL be thrown.
*
* You SHOULD be able to instantiate this class more than once when unit
* testing.
*
*
*/
public class LifeCycle {
private static final AtomicInteger cnt = new AtomicInteger( 0 );
@NotNull
public static LifeCycle getNewLifeCycle() {
if ( cnt.incrementAndGet() > 1 && App.isRealApp() ) {
throw new IllegalStateException("Class is already instantiated");
}
return new LifeCycle();
}
}
其中App.isRealApp()在进行单元测试时应始终返回false,在实际应用程序运行时应始终返回true
我的问题很简单:它有意义吗?我应该如何实现它?如果您的设计需要单例,那么您最好使用单例,而不是尝试复杂的东西 例如,如果您在单元测试单例时遇到的问题是要模拟一个实例,那么我将创建一个可实例化的轻量级代理,它提供与您的单例相同的接口 代理应该没有逻辑——它应该只将调用映射到单例
然后,您可以在测试中使用它,并使代码库保持单例不变。轻量级代理仍然是测试套件的一部分,不会发布。一个解决方案是使用依赖项注入框架,如Spring 应用程序配置将指定单例实例,但是您的测试用例仍然可以根据需要创建实例并手动注入它们 有关更多信息: 另一种方法是,如果您的代码中已经散布了静态单例引用,则使用系统属性控制其行为,将
singleton.getInstance()
方法转换为真正的工厂。这个系统属性可以简单到一个标志来表示测试正在进行中,也可以复杂到另一个类的名称来作为实际工厂使用(类似于JDK对DocumentBuilderFactory
)
因此,我希望我的应用程序在其生命周期中检测到这样一个对象被实例化两次时立即抛出一个异常
这就是单身汉所执行的
但在单元测试时仍允许对此类对象进行多次实例化
您可以使用
这就是反射API所允许的
您可以直接使用反射API,也可以让dp4j为您注入反射API。您将找到这两个函数的代码。@Anon:+1也。。。我发现非常有趣的是,Spring可以在应用程序级别配置为只允许单例,同时仍然允许单元测试。很酷,我会继续读下去。像Spring这样复杂的框架对于这样一个简单的问题来说不是太复杂了吗?我认为答案很清楚。您应该编写测试代码,以便能够测试代码,而不是更改代码以使其可测试。Spring可能有点多,但控制实例创建的工厂在这方面基本上是相同的,而且更轻量级,只要重构负载不是太高。@Roflcoptr-如果你不让你的代码可测试,那么你所能做的测试量就会受到限制。例如,如果您的单例表示一个数据库连接,则必须始终具有该连接才能运行测试。您不能编写一个测试在连接可用时执行行为,另一个测试在连接不可用时执行行为,或者在测试中途失败时执行行为。@Anon:是的,我认为您应该测试代码,但是我认为你不应该仅仅为了测试而修改你的produciton代码。这对我来说很有效——这意味着我可以保持原样。