Java 基于枚举的单例实现和单元测试,将状态保留为枚举的副作用
正如许多人所说,单身是不好的,它是一种“反模式”。不管幸运与否,我使用的一些代码确实有它,而且在不久的将来它不会消失。所以我的问题如下 有一个状态的独生子女:Java 基于枚举的单例实现和单元测试,将状态保留为枚举的副作用,java,junit,enums,singleton,Java,Junit,Enums,Singleton,正如许多人所说,单身是不好的,它是一种“反模式”。不管幸运与否,我使用的一些代码确实有它,而且在不久的将来它不会消失。所以我的问题如下 有一个状态的独生子女: import java.util.HashMap; import java.util.Map; public enum Singleton { INSTANCE; private final Map<String, String> _mapper = new HashMap<String, Strin
import java.util.HashMap;
import java.util.Map;
public enum Singleton {
INSTANCE;
private final Map<String, String> _mapper = new HashMap<String, String>();
public String getValue(String key) {
return _mapper.get(key);
}
public void addEntry(String key, String val) {
_mapper.put(key, val);
}
public String removeKey(String key) {
return _mapper.remove(key);
}
}
如何在两次测试运行之间清除状态?这可以通过青少年跑步者来完成吗
对象的实际状态更复杂,并通过构造函数初始化。我一直在寻找类似这样的东西:您可以使用一个在执行每个测试后都将执行的方法,该方法在执行之后使用
@After
。在此方法中,可以清除枚举中映射的状态:
@After
public void clear() {
Singleton.INSTANCE.removeKey(KEY_1);
Singleton.INSTANCE.removeKey("key_2");
//and on and on...
}
当然,更好的方法是在enum
中使用clear
方法来清除映射的值,因此@After
方法的主体将变得更短:
@After
public void clear() {
//assuming you can create such method
Singleton.INSTANCE.clearEntries();
}
一个选项是在枚举上包含一个包private
clear
方法。这并不是完全的傻瓜式,但这种方法应该足够简单,不会有任何错误。在您的示例中,它只需调用\u mapper.clear()
另一个选项是使用与Singleton
相同的方法创建一个类Delegate
,并让Singleton
仅委托给该类的一个实例。然后,测试Delegate
(而不是Singleton
),每次实例化一个新的
public enum Singleton {
final Delegate delegate = new Delegate();
public String getValue(String key) {
return delegate.getValue(key);
}
// etc
}
class Delegate { // probably best as package-private
private final Map<String, String> _mapper = new HashMap<String, String>();
public String getValue(String key) {
return _mapper.get(key);
}
// etc
}
公共枚举单例{
最终委托=新委托();
公共字符串getValue(字符串键){
返回delegate.getValue(键);
}
//等
}
类委托{//可能最好作为包私有
私有最终映射_mapper=new HashMap();
公共字符串getValue(字符串键){
返回_mapper.get(键);
}
//等
}
这将为您提供一个具有非全局状态的委托
类,该类更易于测试。Singleton
类中的全局状态是它在枚举的类加载时创建的全局Delegate
实例
当然,测试使用<代码> Stutelon < /C> >的任何类都将是棘手的,但这似乎不是你的问题。
这里有一些你可能要考虑的问题。
enum Singleton {
INSTANCE;
ThreadLocal<Map<String, String>> context = new ThreadLocal<Map<String, String>>();
private final Map<String, String> _mapper = new HashMap<String, String>();
void createContext() {
context.set(new HashMap<String, String>());
}
Map<String, String> getMapper() {
Map<String, String> mapper;
mapper = context.get();
if (mapper==null) {
mapper = _mapper;
}
return mapper;
}
public String getValue(String key) {
return getMapper().get(key);
}
// ...
}
enum单例{
实例;
ThreadLocal上下文=新的ThreadLocal();
私有最终映射_mapper=new HashMap();
void createContext(){
set(新的HashMap());
}
映射getMapper(){
地图制图器;
mapper=context.get();
if(映射器==null){
映射器=_映射器;
}
返回映射器;
}
公共字符串getValue(字符串键){
返回getMapper().get(键);
}
// ...
}
在测试中,您将在设置过程中调用INSTANCE.createContext()
如果从未调用
createContext
,则不会创建线程局部变量,因此使用普通的映射器。否则,将检索并使用线程局部变量。clear()将是故事的一部分,因为实际状态更复杂,只有一个片段足够简单,可以重现我的问题。我在回答的第二部分进行了扩展,这听起来对你来说可能是更好的选择。我们将讨论单实例持有者模式。在这种情况下,我知道一个清理状态的解决方法。因为首先使用enum是没有用的。我可以拿起holder解决方案,用反射来清理它。或者只是使用每个测试用例创建的holder实例。但我认为您不需要枚举?我从你的问题中了解到,你不喜欢单身,但你却被它束缚住了(我猜这太痛苦了,不可能把它撕下来,传给所有需要它的人?)。如果这是真的,那么一个解决方案在测试时去掉枚举,但保留它用于遗留代码,有什么不对呢?clear()将是故事的一部分,因为实际状态更复杂,什么是只有一个足够简单的片段来重现我的问题。@ArtemOboturov发布真实的代码以获得更准确的答案。真正的问题是:为什么要在测试之间清除状态?假设我要启动一个paraller runner,您的方法将失败。@ArtemOboturov您首先问如何清除测试运行之间的状态,现在问为什么要这样做?说真的,我还是不明白你真正的问题。此外,如果你不使用我的方法启动一个并行的跑步者,那么测试也会失败。@阿特莫博图洛夫,为什么你不认为它是一个解决方案?这种方法的确切问题是什么?如果Singleton中上下文敏感的成员函数很少,您可以尝试使用ThreadLocal
变量。如果设置了变量,请使用ThreadLocal
上下文,否则请使用全局上下文。我认为您需要对问题进行编辑,以便更准确地了解哪些类型的解决方案是可接受的,哪些不可接受,以及为什么不可接受。很难猜测您似乎没有告诉我们的约束。那么测试将测试什么呢?我想是两个不同的实现。除了映射之外,实现是相同的。除了如何检索映射器之外,实现完全相同。如果您设置了一个上下文,则实例将使用该上下文,但在其他情况下不会有任何更改。如果您在测试之间清除它,您将使用不同的映射。@ArtemOboturov理论上您可以调用INSTANCE.createContext()在应用程序的主线程中,测试完全相同的实现,但会丢失singleton的惰性初始化属性。
enum Singleton {
INSTANCE;
ThreadLocal<Map<String, String>> context = new ThreadLocal<Map<String, String>>();
private final Map<String, String> _mapper = new HashMap<String, String>();
void createContext() {
context.set(new HashMap<String, String>());
}
Map<String, String> getMapper() {
Map<String, String> mapper;
mapper = context.get();
if (mapper==null) {
mapper = _mapper;
}
return mapper;
}
public String getValue(String key) {
return getMapper().get(key);
}
// ...
}