创建不可变的最佳设计方法 Class

Best design approach for creating Immutable Class

我正在阅读在 Effective Java 中创建不可变 Class 时需要遵循的具体指南。

我读到在 Immutable class 中方法不应被覆盖,否则被覆盖的方法可能会改变方法的行为。以下是 java 中可用的设计方法来解决这个问题:-

  1. 我们可以将 class 标记为 final,但根据我的理解,它有一个缺点,即它使 class 不可扩展。

  2. 其次是使个别方法成为最终方法,但除了我们需要单独将每个方法标记为最终方法以防止覆盖之外,我无法得到其他缺点。

  3. 根据本书,更好的方法是将构造函数设为私有或包私有,并提供 public 用于创建对象的静态工厂方法。

我的问题是:即使我们在 class 中包含私有或默认构造函数,它也不能再在同一个包中扩展(在包私有构造函数的情况下在其他包中),它具有相同的第一个遇到的问题。它如何被认为是比以前更好的方法?

提供静态工厂方法为您提供了实现享元模式的空间。

他们说你应该隐藏使用构造函数创建新对象的可能性,而应该调用一个方法来检查 "object pool" 中是否存在具有相似状态的对象(一张充满等待重新使用的对象的地图)。不重用不可变对象是一种内存浪费;这就是为什么 String 文字被鼓励, new String() 被回避(除非需要)。

class ImmutableType {
    private static final Map<Definition, ImmutableType> POOL = new HashMap<>();

    private final Definition definition;

    private ImmutableType(Definition def) {
         definition = def;
    }

    public static ImmutableType get(Definition def) {
         if(POOL.contains(def))
              return POOL.get(def);
        else {
              ImmutableType obj = new ImmutableType(def);
              POOL.put(def, obj);

              return obj;
        }
    }
}

Definition 存储 ImmutableType 的状态。如果池中已经存在具有相同定义的类型,则重新使用它。否则,创建它,将它添加到池中,然后 return 它作为值。

关于标记 class final 的声明,不可变类型首先不应是可扩展的(以避免可能的修改行为)。标记每个方法 final 对于不可变 classes 来说简直是疯了。

不可变对象不应该是可扩展的。为什么?

因为扩展它将允许直接访问字段(如果它们是 protected 这将允许编写更改它们的方法),或者添加可能可变的状态。

假设我们写了一个扩展 Double 的 class FlexiblyRoundableDouble,它有一个额外的字段 roundingMode 让我们选择一个 "rounding mode"。你可以为这个字段写一个 setter,现在你的对象是可变的。

你可以争辩说,如果所有的方法都设置为final,你就不能改变对象的原始行为。唯一可以访问 roundingMode 字段的方法是新方法,如果您将对象分配给 Double 变量,则这些方法不能多态使用。但是当 class 的合同说它是不可变的时,你就可以根据它做出决定。例如,如果您为具有 Double 字段的 class 编写 clone() 方法或复制构造函数,您知道不需要深复制 Double字段,因为它们不会改变它们的状态,因此可以在两个克隆之间安全地共享。

此外,您可以编写 return 内部对象的方法,而不必担心调用者随后会更改该对象。如果该对象是可变的,则您必须对其进行 "defensive copy"。但如果它是不可变的,那么 return 对实际内部对象的引用是安全的。

但是,如果有人将 FlexiblyRoundableDouble 分配给您的 Double 字段之一,会发生什么情况?该对象将是可变的。 clone() 会假设它不是,它将在两个对象之间共享,甚至可能 return 由一个方法共享。然后,调用者可以将其转换为 FlexiblyRoundableDouble,更改字段...这将影响使用同一实例的其他对象。

因此,不可变对象应该是最终对象。


这一切都与构造函数问题无关。对象可以使用 public 构造函数安全地保持不变(如 StringDoubleInteger 和其他标准 Java 不可变对象所证明的那样)。静态工厂方法只是利用对象不可变这一事实的一种方式,其他几个对象可以安全地持有对它的引用,从而创建更少的具有相同值的对象。