【问题标题】:Java constructor of "immutable class" with many fields with default values?具有许多具有默认值的字段的“不可变类”的Java构造函数?
【发布时间】:2012-07-07 21:54:00
【问题描述】:

我有一个包含很多字段的 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. 创建一个新的可变参数类并将该类用作构造函数。

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

【问题讨论】:

    标签: java oop design-patterns


    【解决方案1】:

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

    【讨论】:

    • 你的构建器中甚至不需要getter,A的构造器可以是私有的。这也允许检查 build() 方法而不是构造函数中的参数。
    • 是的。我考虑将 A 的 ctor 设为私有。那可能更干净。
    • 另一个优点是构建器可以根据参数选择返回 A、ABis 或 ATer 的实例(Abis 和 Ater 是 A 的子类)。
    • @TomAnderson 你可以让他们保密;外部类仍然可以访问它们。
    • @zggame:如果你愿意走那么远,“灵丹妙药”就是使用另一种语言,例如命名和默认参数,例如Scala
    【解决方案2】:

    你可以做两件事:

    【讨论】:

    • 我认为构建器模式不适合不可变类。
    • @zggame:当然不是!您使用构建器来构建另一个对象。
    【解决方案3】:

    这只是一个半严肃的建议,但我们可以将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 行的通用构建器,每个实体都有一个子类,包含一个单行方法。

    【讨论】:

    • 不错。我喜欢静态类型。很好的折衷方案,我也许可以将它用于我们有一个巨大的 Map 的另一种情况。谢谢。
    • 您实际上不需要在退出时强制转换 Class 实例,因为类型安全是通过 put 方法强制执行的,要求属性类型和值匹配。可以在不影响安全性的情况下使用未经检查的演员表。省略使用类对象的优点是它适用于具有泛型的对象,例如 List,您无法提供类型安全的类对象。
    【解决方案4】:

    一个有趣的选择是创建一个以Map&lt;String,Object&gt; 作为输入的构造函数,其中包含用户想要指定的值。

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

    编辑:

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

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

    【讨论】:

    • 这会导致许多错误,主要是类型兼容性,我想说在这种情况下,构建器方法会更好(如 Jordão 所建议)
    • 嗯,这是一种来自动态语言的技术……您正在交易静态类型检查以换取方便/灵活性。如果您喜欢这种方法,由您决定,但我发现如果您编写好的测试,这不是问题。
    • 这是 JavaScript 的方式,在动态类型的语言中很好,但我不会在 Java 中这样做。如果所有参数都是相同的类型,可能会起作用。不过,我认为 Bob Martin 对“传递散列映射”不是一个好主意有话要说。既不是 -1 也不是 +1。
    • 在我看来,这样的方法违背了 Java 所代表的一切。
    • 有一些方法可以做这样的事情,但还不错(例如,定义一个枚举对象属性的枚举,并将其实例用作键),但它可能并不比建设者。
    【解决方案5】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-24
      相关资源
      最近更新 更多