不可变类/对象、私有构造函数、工厂方法

Posted

技术标签:

【中文标题】不可变类/对象、私有构造函数、工厂方法【英文标题】:Immutable class/object, private Constructor, factory method 【发布时间】:2012-11-18 22:21:26 【问题描述】:

已经阅读了如何通过以下步骤使类不可变

    不提供“setter”方法——修改字段或字段引用的对象的方法。 将所有字段设为最终字段和私有字段。 不允许子类覆盖方法。最简单的方法是将类声明为 final。更复杂的方法是将构造函数设为私有并在工厂方法中构造实例。 如果实例字段包含对可变对象的引用,请不要更改这些对象: 一种。不要提供修改可变对象的方法。 湾。不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,在必要时创建内部可变对象的副本以避免在方法中返回原始对象。

我不确定我是否清楚地了解私有构造函数和工厂方法在不变性背景下的实用性。如果我将课程定为最终课程,则基本上是关闭扩展它的任何其他课程的所有路径。所说的更复杂的方法如何

我已经看到了单例模式中的私有构造函数、工厂方法,这是有道理的。但是当我们谈到对象不变性时,当我们提到私有构造函数和静态工厂方法时,我们是否也在限制对象构造/实例化??

【问题讨论】:

是的。做 (2) 和 (4) 应该足以使实例不可变。执行 (3) 会使类无法扩展(或者至少,任何子类都不能实例化),这是完全不同的。 【参考方案1】:

仔细阅读以下几点

不允许子类覆盖方法。最简单的方法是声明类 final。更复杂的方法是将构造函数设为私有并在工厂方法中构造实例。

我认为这里的重点不是

使类最终和私有构造函数。

重点是

要么使你的类成为最终类,要么拥有一个私有构造函数。

希望对你有帮助!

source

【讨论】:

【参考方案2】:

私有构造函数背后的想法是,您希望隐藏类数据的不可变实现,同时允许构造具有相同内部类型的不同数据的新实例。

例如

public class Temperature

    private readonly double temperatureInCelsius;

    public Temperature(string temperatureInCelsius)
    
         this.temperatureInCelsius = System.Convert.ToDouble(temperatureInCelsius);
    
    private Temperature(double temperatureInCelsius)
    
         this.temperatureInCelsius = temperatureInCelsius;
    

    public Temperature AddCelsius(string temperatureToAddInCelsius)
    
         return new Temperature(System.Convert.ToDouble(temperatureToAddInCelsius) + temperatureInCelsius);
    
    public void PrintCelsius(Display display)
    
        display.Print(temperatureInCelsius);
    
    public void PrintFarenheit(Display display)
    // ... etc

忽略示例的半愚蠢,如果您的要求是可以从表示温度的字符串构造类。它实际存储温度的方式可能会发生变化,并且是一个实现细节。此类可以更改为使用浮点数、字符串、双精度数、整数等。此方法保持不变性,同时允许实现灵活性。显然,当您包装更复杂的对象(如集合、字典等)时,这会变得更加强大。

【讨论】:

使用包私有构造函数的一个基本优点是带有公共构造函数的类Foo 承诺调用new Foo(whatever) 的代码将获得一个新的对象实例,其确切类型将是@ 987654324@,但许多不可变类可能不想做出这样的承诺。如果期望对相同温度的许多请求的类作者可能包含静态WeakDictionary<String,Temperature>,那么使用相同字符串的重复请求将产生相同的温度。公开一个公共构造函数会使这样的事情变得不可能。【参考方案3】:

我认为这里的大问题是未来的重构。假设,在以后的版本中,如果您可以将 MyClass 的一些新特例拆分成一个子类 MySpecialClass,这会使事情变得更简单。

如果 MyClass 是具有公共构造函数的可变类,您可以这样做并告诉用户新功能创建一个新的 MySpecialClass。现有用途不受影响。

如果 MyClass 有私有构造函数和工厂方法,就没有问题。您声明 MySpecialClass 嵌套在 MyClass 中,也使用私有构造函数。添加和/或修改工厂方法以选择要创建的方法,但要确保现有调用继续兼容地工作。

如果 MyClass 是不可变的、最终的,但有一个公共构造函数,你会怎么做?

【讨论】:

我认为如果MyClass是MySpecialClass的超类,是不是MyClass的构造函数需要被保护? 谢谢 - 我会更正我的答案 - 需要在 MyClass 中声明 MySpecialClass 并对其进行扩展,以便访问私有构造函数。【参考方案4】:

我自己在Effective Java第15条中的一些发现,粘贴了相同的相关陈述

“回想一下 为了保证不变性,一个类不能允许自己被子类化。 通常这是通过将类设为 final 来完成的,但还有另一种更灵活的方法 方法来做到这一点。使不可变类最终的替代方法是使所有 它的构造函数私有或包私有,并添加公共静态工厂 公共构造函数的位置(第 1 项)。

虽然这种方法并不常用,但它通常是最好的选择。它是 最灵活,因为 它允许使用多个包私有实现 对于驻留在其包之外的客户,不可变类是 有效地是最终的,因为不可能扩展来自另一个类的类 包,并且缺少公共或受保护的构造函数。 除了允许 多个实现类的灵活性,这种方法可以 通过改进对象缓存在后续版本中调整类的性能 静态工厂的能力。

"

然后讨论静态工厂与构造函数的优势

【讨论】:

他如何证明他的主张,“为了保证不变性,一个类不能允许自己被子类化”? @DavidWallace 来自 Effective Java,说明了使类不可变的规则之一 2. 确保类不能被扩展。这可以防止粗心或恶意的子类通过表现得好像对象的状态已更改来损害类的不可变行为。防止子类化通常是通过将类设为 final 来完成的,但我们稍后会讨论另一种选择。显然是私有构造函数,工厂方法是另一种选择 但是将所有字段设置为 final 和 private 也会防止讨厌的子类做讨厌的事情。在我看来,这有点矫枉过正。 好吧..在这个方向上再多一个指针..这个替代方案将确保不可变类实例的重用..就像单例一样..不可变对象也是线程安全的 @DavidWallace 我相信他的观点是子类可以添加自己的字段,可变类型,setter方法等。【参考方案5】:

IMMUTABILITY - 对并发非常有帮助,因为它避免了在线程环境中可能创建各种 invariant

工厂方法 - 只是一种命名约定,因为它们的自定义名称更冗长易于阅读和理解。例如:copyOf() 方法比创建复制构造函数更有意义。正如 Joshua Bloch 在 Effective Java 中所说的

PRIVATE CONSTRUCTORS - 它们在像 Singleton 这样的模式中有自己的用途,但也有自己的局限性。

【讨论】:

【参考方案6】:

首先,不可变类一般不应该被覆盖有几个原因,你可以找到它们here。

也就是说,将构造函数设为私有只是防止类被覆盖的一种方法。为什么?因为在子类中,每个构造函数(隐式)调用super(),基类的默认构造函数。但是,如果您将此构造函数设为私有,子类将无法调用它,因此无法覆盖基类。当您想要控制特定类的实例总数时,这种方法非常适合,例如在单例的情况下。

【讨论】:

如果你在超类中将构造函数设为私有,那么创建子类没有任何意义,是吗?因为如果您确实有一个子类并且您的超类有一个私有构造函数,那么它将把它的厄运视为编译时错误.. 但是我确实看到了不可变性和不可扩展性之间的联系。子类化可能会破坏不可变的纯度。至少我可以从您发布的链接中看出这一点 不变性与私有构造函数或工厂方法无关。 JDK 中的许多类都有工厂方法和公共构造函数。 我认为,如果您对特定类的不变性有要求,您需要考虑这是否也适用于其子类。是否应该有子类;如果是这样,任何子类都应该是可实例化的吗?如果是这样,这些实例应该是完全不可变的,只对基类中的成员不可变,还是根本不可变?在考虑是否需要私有构造函数或最终声明之前,您需要做出决定。 @HungryForKnowledge - 确切地说,并不是没有意义,没有方式来创建子类,这就是在这种情况下私有构造函数的目的。子类化可以破坏不可变类的安全性,这基本上是阻止它的主要原因,但该链接中列出的内容很少。【参考方案7】:

这是因为使用私有构造函数我们不能创建它的子类,因此限制了任何其他扩展它的类的所有路径。它很复杂,因为它有自己的局限性/复杂性,比如单例模式。

【讨论】:

final 类不能扩展。 :-(...因此,您将通过将其定为最终来限制要扩展的类... 是的,这就是为什么让 final 是最简单的方法,而让构造函数私有是更复杂的方法。 你将如何创建实例?使用工厂方法,但是它将如何提供帮助?复杂性不会使代码复杂化... 是的,使用工厂方法。使用工厂方法无济于事,但私有构造函数有助于实现不变性。使用私有构造函数时,还有其他事情要做,但没有帮助。私有构造函数只是实现不变性的方法之一,并不是最好的。 好吧,这与标记班级决赛有什么不同?你能改变那个班级的状态吗?如果我们试图实现与字符串非常相似的东西,我可以理解,否则不需要使代码复杂化【参考方案8】:

是的,你是对的。将构造函数设为私有没有任何意义。通过这样做,我们限制了实例的创建,这不是不变性的理想场景。

在 sun 站点中提到的示例中,没有将构造函数设为私有 http://docs.oracle.com/javase/tutorial/essential/concurrency/syncrgb.html

【讨论】:

这是一个同步类示例..我想你的意思是docs.oracle.com/javase/tutorial/essential/concurrency/… 对...虽然类是不可变的,但它们并没有降低构造函数的可见性。我认为将构造函数设为私有会增加代码的开销。

以上是关于不可变类/对象、私有构造函数、工厂方法的主要内容,如果未能解决你的问题,请参考以下文章

第四章 对象与类

Effective Java 5.避免创建重复的对象

Scala编程--函数式对象

effectiveJava避免创建不必要的对象

java中的不可变类型的探究

Mybatis动态SQL