我正在研究一些从全局变量中获取部分配置的类,例如
class MyClass {
public void MyClass(Hashtable<String, String> params) {
this.foo = GlobalClass.GLOBALVAR.get("foo");
this.bar = GlobalClass.GLOBALVAR.get("bar");
this.params = params;
}
}
由于几个原因,这是不好的,GLOBALVAR与数据库进行对话以获取一些变量,这使得进行单元测试变得非常困难。另一个问题是我有很多(几十个)继承自MyClass的类,所以我不能轻易地改变构造函数签名。
我目前的解决方案是为params
,foo
和bar
创建其他默认构造函数和setter方法。
class MyClass {
// Other code still here for backwards compatibility.
public void MyClass() {
// Do nothing much.
}
public void setParams(Hashtable<String, String> params) {
this.params = params;
}
public void setFoo(Foo foo) {
this.foo = foo;
}
public void setBar(Bar bar) {
this.bar = bar;
}
}
除了我这样做的方式之外,还有一个关于重构这个的好方法的想法吗?我的另一个想法是使用工厂方法,但我担心我会遇到多态替换问题。
答案 0 :(得分:3)
我认为你应该引入一个接口,在全局变量集合和它的使用者之间放置一层抽象。
interface GlobalVars {
String get(String key);
}
您应该引入范围有限的构造函数,可能是package-private
MyClass(GlobalVars globals, Map<String, String> params) {
// create the object
}
然后提供public static
工厂方法来使用此构造函数。
public static MyClass newMyClass(Map<String, String> params) {
return new MyClass(GlobalClass.GLOBAL_VAR, params);
}
使用此设计,您可以通过显式调用构造函数在同一个包中的单元测试中传递GlobalVars
的模拟实现。
附录:由于params
似乎是必填字段,因此我肯定会将其设为final
,并避免使用添加mutators来覆盖它们的方法。
private final Map<String, String> params;
另外,制作防御性副本以防止l33t h4x。
this.params = Collections.unmodifiableMap(params);
答案 1 :(得分:3)
我想我会先做以下几点。它让您现有的代码无需修改即可工作,并允许您尽可能地向子类添加新的构造函数。一旦所有子类都具有新的构造函数,并且对旧构造函数的所有调用都消失了,您就可以摆脱GlobalClass和使用它的构造函数。然后,您也可以继续清理GLOBALVAR(我的代码中的Car类)。
import java.util.Hashtable;
class MyClass
{
private final Foo foo;
private final Bar bar;
private final Hashtable<String, String> params;
public MyClass(final Hashtable<String, String> params)
{
this(params, GlobalClass.GLOBALVAR);
}
// added constructor
public MyClass(final Hashtable<String, String> params,
final FooBar fooBar)
{
this.foo = fooBar.getFoo();
this.bar = fooBar.getBar();
this.params = params;
}
}
class MySubClass
extends MyClass
{
public MySubClass(final Hashtable<String, String> params)
{
super(params);
}
// added constructor
public MySubClass(final Hashtable<String, String> params,
final FooBar fooBar)
{
super(params, fooBar);
}
}
// unchanged
class GlobalClass
{
public static Car GLOBALVAR;
}
// added interface
interface FooBar
{
Foo getFoo();
Bar getBar();
}
class Car
// added implements
implements FooBar
{
private Foo foo = new Foo();
private Bar bar = new Bar();
public Object get(final String name)
{
if(name.equals("foo"))
{
return (foo);
}
if(name.equals("bar"))
{
return (bar);
}
throw new Error();
}
// added method
public Foo getFoo()
{
return ((Foo)get("foo"));
}
// added method
public Bar getBar()
{
return ((Bar)get("bar"));
}
}
// unchanged
class Foo
{
}
// unchanged
class Bar
{
}
答案 2 :(得分:2)
您的类应该在构造函数中使用它的所有依赖项。最好不要创建一个无效或未初始化的类实例。将foo
和bar
设为private和final,并在构造函数中设置它们。
答案 3 :(得分:1)
您的方法略有不同,就是在类中使用GLOBALVAR类型的对象并使用它而不是实际的全局(重构应该是一个简单的搜索/替换)。您可以将新变量默认为实际的全局变量,并为测试提供覆盖。
答案 4 :(得分:0)
应将此GlobalClass.GLOBALVAR切换为逻辑单元。这样,为单元测试制作模拟对象会更容易。例如,在我的CAD / CAM金属切割应用程序中,我有一个MaterialList,一个SheetSizeList,PartNestingParameters等。
我没有大量的变量列表到一个巨大的AppParameter类中。它们都挂在ShopStandards对象上。对于涉及特定PartNestingParmeters的单元测试,我将去ShopStandards.PartNestingParmeters = new MockPartNestingParameterTest114()。测试将没有意识到部件嵌套参数是一个模型。此外,这使我无需进行数十项任务,只是为了正确测试ShopStandard设置。
我们有更多的自动化,在初始开发过程中,在测试运行期间保存的文件中有许多Mock加载。
答案 5 :(得分:0)
由于您提到您可以自由修改类层次结构。