3

我正在研究一些从全局变量中获取部分配置的类,例如

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和. 创建一个额外的默认构造函数和 setter 方法bar

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;
     }
}

除了我这样做的方式之外,还有什么关于重构它的好方法的想法吗?我的另一个想法是使用工厂方法,但我担心我会遇到多态替换问题。

4

6 回答 6

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并避免使用添加变异器来覆盖它们的方法。

private final Map<String, String> params;

另外,制作防御副本以防止 l33t h4x。

this.params = Collections.unmodifiableMap(params);
于 2009-04-07T17:26:25.817 回答
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
{
}
于 2009-04-07T17:58:53.210 回答
2

您的类应该在构造函数中获取其所有依赖项。最好不要创建无效或未初始化的类实例。makefoobarprivate 和 final,并在构造函数中设置它们。

于 2009-04-07T17:23:47.367 回答
1

您的方法的一个细微变化是在类中有一个 GLOBALVAR 类型的对象并使用它而不是实际的全局对象(重构应该是一个简单的搜索/替换)。您可以将新变量默认为实际的全局变量,并为测试提供覆盖。

于 2009-04-07T17:19:50.807 回答
0

这个 GlobalClass.GLOBALVAR 应该被分割成逻辑单元。这样,为单元测试制作模拟对象会更容易。例如,在我的 CAD/CAM 金属切削应用程序中,我有一个 MaterialList、一个 SheetSizeList、PartNestingParameters 等。

我没有将一大堆变量放入一个巨大的 AppParameter 类中。它们都挂在 ShopStandards 对象上。对于涉及特定 PartNestingParmeters 的单元测试,我将只使用 ShopStandards.PartNestingParmeters = new MockPartNestingParameterTest114()。测试将在没有意识到零件嵌套参数是模型的情况下运行。另外,这让我不必为了测试正确设置 ShopStandard 而做几十个作业。

我们更加自动化,其中许多 Mock 从初始开发期间测试运行期间保存的文件加载。

于 2009-04-07T17:26:45.267 回答
0

既然您提到您可以自由修改类层次结构。

  • 更改基本 MyClass ctor 以接收 3 个参数 params、foo 和 bar。注释掉 GlobalVar 引用并简单地缓存传入的值
  • 编译..这应该会引发一堆编译错误 - 没有采用 1 个参数的 ctor。
  • 修复每一项以传入 GlobalVar.get("foo") 和 GlobalVar.get("bar")。得到它来建造。
  • 优化:现在通过延迟加载和缓存 foo 和 bar 值来最小化对 DB 的命中。通过 GlobalVar 上的一些属性公开。
于 2009-04-07T18:31:02.180 回答