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

时间:2012-11-30 16:55:43

标签: java singleton immutability

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

  1. 不提供“setter”方法 - 修改字段引用的字段或对象的方法。
  2. 将所有字段设为最终和私有。
  3. 不允许子类覆盖方法。最简单的方法是将类声明为final。更复杂的方法是使构造函数成为私有的,并在工厂方法中构造实例。
  4. 如果实例字段包含对可变对象的引用,则不允许更改这些对象:
    一个。不要提供修改可变对象的方法 湾不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始对象。
  5. AM我不确定我是否清楚地了解私有构造函数和工厂方法在不变性方面的效用。如果我让类最终,基本上是关闭任何其他类扩展它的所有路径。如何陈述更复杂的方法

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

8 个答案:

答案 0 :(得分:2)

首先,有几个原因通常不应该覆盖不可变类,你可以找到它们here

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

答案 1 :(得分:2)

我认为这里的重大问题是未来的重构。假设,在更高版本中,如果您可以将MyClass的一些新的特殊情况拆分为子类MySpecialClass,那么您会发现它会更简单。

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

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

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

IMMUTABILITY - 非常有助于并发性,因为它可以避免在线程环境中创建各种 不变

工厂方法 - 只是一个命名惯例,因为它们更易于阅读详细,并且可以通过自定义名称轻松理解。例如:copyOf()方法比创建复制构造函数更有意义。正如有效Java中 Joshua Bloch 所述

私人建构者 - 他们在像Singleton这样的模式中有自己的用途,但也有其自身的局限性。

答案 4 :(得分:0)

我自己的一些发现来自 Effective Java 第15项,粘贴相同的声明

“回想一下 为了保证不变性,一个类不得允许自己进行子类化。 通常这是通过使类最终完成,但还有另一个,更灵活 这样做的方式。使一个不可变的类最终的替代方法是使所有的 它的构造函数private或package-private,以及添加公共静态工厂 公共建设者的地方(第1项)。

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

然后讨论

静态工厂与构造函数的优点

答案 5 :(得分:0)

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

例如

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
}

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

答案 6 :(得分:0)

彻底阅读以下内容后

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

我认为这里的重点不是

制作类final和私有构造函数。

重点是

要么让你的班级成为最终版本,要么拥有私人构造函数。

希望它有所帮助!!

source

答案 7 :(得分:-1)

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