22

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

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

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

  1. 我需要很多具有不同签名的构造函数。
  2. 创建一堆这些字段的设置方法,只设置那些非默认值。但这以某种方式表明了与不可变性质不同的语义。
  3. 创建一个新的可变参数类并将该类用作构造函数。

这些都不是完全令人满意的。还有其他方法吗?谢谢。单程

4

5 回答 5

27

我将使用参数类和流利的构建器 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类不可变,参数可选,接口流畅。

于 2012-07-07T22:06:10.953 回答
19

你可以做两件事:

于 2012-07-07T21:56:12.690 回答
1

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

假设我们有:

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构造函数的方法,本质上把它变成一个构建器。

您还可以使其中一些对象更通用。AProperty并且APropertyMap可以具有执行功能位的通用基类,以及非常简单的A特定子类。

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

于 2012-07-07T23:14:07.157 回答
0

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

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

编辑:

我认为随机投票者完全忽略了这一点——这并不总是最好的选择,但它是一种有用的技术,有几个优点:

  • 它简洁,避免了创建单独的构造函数/构建器类的需要
  • 它允许参数集的简单编程构造(例如,如果您正在从解析的 DSL 构造对象)
  • 这是一种经常使用并被证明可以在动态语言中工作的技术。您只需要编写体面的测试(无论如何您都应该这样做!)
于 2012-07-07T22:00:33.947 回答
0

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

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

于 2012-07-07T23:01:52.543 回答