“不可变类”的Java构造函数,其中包含许多具有默认值的字段?

时间:2012-07-07 21:54:01

标签: java oop design-patterns

我有一个包含大量字段的JAVA类。它们基本上应该在构造函数阶段设置,永远不会改变。从语义上讲,这个类是不可变的。

public class A{
    final int a;
    final short b;
    final double e;
    final String f;
    final String g;
    //and more
}

问题是通常这些字段具有默认值,因此我不想总是给用户带来所有这些字段的构造函数。大多数时候,他们只需要设置几个。有几种方法可以解决这个问题:

  1. 我需要很多具有不同签名的构造函数。
  2. 创建一堆这些字段的set方法,只设置那些非默认值。但这在某种程度上表明了除了不可变性质之外的不同语义。
  3. 创建一个可变的新参数类,并将该类用作构造函数。
  4. 这些都不是完全令人满意的。还有其他方法吗?谢谢。 一种方式

5 个答案:

答案 0 :(得分:27)

我会使用参数类和fluent构建器API的组合来创建参数:

public class A {
    private final int a;
    private final short b;
    private final double e;
    private final String g;

    public static class Aparam {
        private int a = 1;
        private short b = 2;
        private double e = 3.141593;
        private String g = "NONE";

        public Aparam a(int a) {
            this.a = a;
            return this;
        }

        public Aparam b(short b) {
            this.b = b;
            return this;
        }

        public Aparam e(double e) {
            this.e = e;
            return this;
        }

        public Aparam g(String g) {
            this.g = g;
            return this;
        }

        public A build() {
            return new A(this);
        }
    }

    public static Aparam a(int a) {
        return new Aparam().a(a);
    }

    public static Aparam b(short b) {
        return new Aparam().b(b);
    }

    public static Aparam e(double e) {
        return new Aparam().e(e);
    }

    public static Aparam g(String g) {
        return new Aparam().g(g);
    }

    public static A build() {
        return new Aparam().build();
    }

    private A(Aparam p) {
        this.a = p.a;
        this.b = p.b;
        this.e = p.e;
        this.g = p.g;
    }

    @Override public String toString() {
        return "{a=" + a + ",b=" + b + ",e=" + e + ",g=" + g + "}";
    }
}

然后像这样创建A的实例:

A a1 = A.build();
A a2 = A.a(7).e(17.5).build();
A a3 = A.b((short)42).e(2.218282).g("fluent").build();

A类是不可变的,参数是可选的,界面很流畅。

答案 1 :(得分:19)

你可以做两件事:

答案 2 :(得分:1)

这只是一个半严肃的建议,但我们可以将mikera's answer修改为类型安全。

说我们有:

public class A {
    private final String foo;
    private final int bar;
    private final Date baz;
}

然后我们写:

public abstract class AProperty<T> {
    public static final AProperty<String> FOO = new AProperty<String>(String.class) {};
    public static final AProperty<Integer> BAR = new AProperty<Integer>(Integer.class) {};
    public static final AProperty<Date> BAZ = new AProperty<Date>(Date.class) {};

    public final Class<T> propertyClass;

    private AProperty(Class<T> propertyClass) {
        this.propertyClass = propertyClass;
    }
}

public class APropertyMap {
    private final Map<AProperty<?>, Object> properties = new HashMap<AProperty<?>, Object>();

    public <T> void put(AProperty<T> property, T value) {
        properties.put(property, value);
    }
    public <T> T get(AProperty<T> property) {
        return property.propertyClass.cast(properties.get(property));
    }
}

高级设计模式和/或模糊Java技巧的狂热爱好者会将此视为一种类型安全的异构容器。非常感激我也没有使用getGenericSuperclass()

然后,返回目标类:

public A(APropertyMap properties) {
    foo = properties.get(AProperty.FOO);
    bar = properties.get(AProperty.BAR);
    baz = properties.get(AProperty.BAZ);
}

这都是这样使用的:

APropertyMap properties = new APropertyMap();
properties.put(AProperty.FOO, "skidoo");
properties.put(AProperty.BAR, 23);
A a = new A(properties);

对于lulz,我们甚至可以为地图提供流畅的界面:

public <T> APropertyMap with(AProperty<T> property, T value) {
    put(property, value);
    return this;
}

让来电者写信:

A a = new A(new APropertyMap()
    .with(AProperty.FOO, "skidoo")
    .with(AProperty.BAR, 23));

你可以对此做很多改进。 AProperty中的类型可以更优雅地处理。 APropertyMap可以有一个静态工厂而不是构造函数,如果你遇到那种东西,可以使用更流畅的代码。 APropertyMap可以增加build方法,该方法调用A的构造函数,实际上将其转换为构建器。

您也可以使其中一些对象更通用。 APropertyAPropertyMap可以使用通用基类来执行功能位,并使用非常简单的A特定子类。

如果您感觉特别是企业,并且您的域对象是JPA2实体,那么您可以使用元模型属性作为属性对象。这使得地图/构建器做了一些工作,但它仍然非常简单;我有一个通用的构建器工作在45行,每个实体的子类包含一个单行方法。

答案 3 :(得分:0)

一个有趣的选择是创建一个构造函数,该构造函数将Map<String,Object>作为输入,其中包含用户想要指定的值。

构造函数可以使用映射中提供的值(如果存在),否则使用默认值。

修改

我认为随机选民已完全忽略了这一点 - 这并不总是最佳选择,但它是一项有用的技术,有几个优点:

  • 它简洁,无需创建单独的构造函数/构建器类
  • 它允许简单的编程构造参数集(例如,如果您从解析的DSL构建对象)
  • 这是一种经常使用并证明可以在动态语言中使用的技术。你只需要编写体面的测试(无论如何你都应该做!)

答案 4 :(得分:0)

拥有许多字段可能表明一个课程做得太多。

也许您可以将类拆分为几个不可变类,并将这些类的实例传递给其他类的构造函数。这将限制构造函数的数量。