重构使用全局变量的类

时间:2009-04-07 17:15:53

标签: java refactoring global-variables

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

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的类,所以我不能轻易地改变构造函数签名。

我目前的解决方案是为paramsfoobar创建其他默认构造函数和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;
     }
}

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

6 个答案:

答案 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)

您的类应该在构造函数中使用它的所有依赖项。最好不要创建一个无效或未初始化的类实例。将foobar设为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)

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

  • 更改基本MyClass ctor以接受3个参数params,foo和bar。注释掉GlobalVar引用并简单地缓存传入的值
  • 编译..这应该会抛出一堆编译错误 - 没有ctor需要1个参数。
  • 修复每一个以传递GlobalVar.get(“foo”)和GlobalVar.get(“bar”)。搞定它。
  • 优化:现在通过延迟加载最小化对数据库的命中并缓存foo和bar值。通过GlobalVar上的某些属性公开。