Java 公开不可变对象的状态可以吗?

Java 公开不可变对象的状态可以吗?,java,immutability,value-objects,Java,Immutability,Value Objects,最近我遇到了不可变对象的概念,我想知道控制对状态的访问的最佳实践。尽管我大脑中面向对象的部分让我在公众面前感到害怕,但我看不到类似这样的技术问题: public class Foo { public final int x; public final int y; public Foo( int x, int y) { this.x = x; this.y = y; } } 我更愿意将字段声明为private,并为每个字段提供g

最近我遇到了不可变对象的概念,我想知道控制对状态的访问的最佳实践。尽管我大脑中面向对象的部分让我在公众面前感到害怕,但我看不到类似这样的技术问题:

public class Foo {
    public final int x;
    public final int y;

    public Foo( int x, int y) {
        this.x = x;
        this.y = y;
    }
}
我更愿意将字段声明为
private
,并为每个字段提供getter方法,但当状态显式为只读时,这似乎过于复杂


提供对不可变对象状态的访问的最佳实践是什么?

这完全取决于您将如何使用该对象。公共字段并非天生邪恶,只是将所有内容默认为公共是不好的。例如,java.awt.Point类将其x和y字段公开,甚至不是最终字段。您的示例似乎很好地使用了公共字段,但您可能不想公开另一个不可变对象的所有内部字段。没有“一网打尽”的规则。

如果您的对象具有足够的本地使用率,以至于您不关心将来中断对它的API更改的问题,那么就没有必要在实例变量之上添加getter。但这是一个一般性的主题,而不是特定于不变的对象


使用getter的优势来自一个额外的间接层,如果您正在设计一个将被广泛使用的对象,并且其实用程序将扩展到不可预知的未来,那么间接层可能会派上用场。

不管不可更改性如何,您仍然在公开此类的实现。在某个阶段,您可能需要更改实现(或者可能会生成各种派生,例如使用Point示例,您可能需要使用极坐标的类似Point类),并且您的客户端代码将暴露于此


上面的模式可能很有用,但我通常会将其限制在非常本地化的实例中(例如,传递信息的元组-我倾向于发现看似不相关的信息的对象要么是不好的封装,要么是相关的信息,并且我的元组会转换为完全成熟的对象)

我使用了许多与您在问题中提出的构造非常相似的构造,有时使用(有时是不可变的)数据结构比使用类可以更好地对某些事物进行建模

这一切都取决于,如果您正在建模一个对象,一个由其行为定义的对象,在这种情况下,永远不要公开内部属性。其他时候,当您对数据结构进行建模时,java没有针对数据结构的特殊构造,可以使用一个类并公开所有属性,如果您想要不可变的final和public,那么当然可以


例如,罗伯特·马丁(robert martin)在《清洁代码》(Clean Code)一书中有一章是关于这一点的,我认为这是一本必读的书。

我只知道有一个道具可以为最终属性提供getter。当您希望通过接口访问属性时就是这种情况

    public interface Point {
       int getX();
       int getY();
    }

    public class Foo implements Point {...}
    public class Foo2 implements Point {...}
否则,公共最终字段就可以了。

只想反映:


那么,
Foo
仍然是不变的吗?:)

我过去也这么认为,但通常会将变量设置为私有,并使用getter和setter,这样以后我仍然可以选择在保持相同接口的同时对实现进行更改

    public interface Point {
       int getX();
       int getY();
    }

    public class Foo implements Point {...}
    public class Foo2 implements Point {...}
这确实让我想起了我最近在罗伯特·C·马丁的《干净的代码》中读到的一些东西。在第六章中,他给出了一个稍微不同的观点。例如,在第95页,他说

“对象将其数据隐藏在抽象后面,并公开对该数据进行操作的函数。数据结构公开其数据,但没有任何有意义的函数。”

第100页:

bean的准封装似乎让一些OO纯粹主义者感觉更好,但通常没有其他好处

根据代码示例,Foo类似乎是一个数据结构。因此,根据我在Clean Code讨论中的理解(这不仅仅是我给出的两个引用),该类的目的是公开数据,而不是功能,拥有getter和setter可能没有多大好处


同样,根据我的经验,我通常会使用私有数据的“bean”方法和getter和setter。但话说回来,没有人让我写一本关于如何编写更好的代码的书,所以也许Martin有话要说。

要记住的一点是函数调用提供了一个通用接口。任何对象都可以使用函数调用与其他对象交互。你所要做的就是定义正确的签名,然后离开。唯一需要注意的是,您必须仅通过这些函数调用进行交互,这些函数调用通常工作得很好,但在某些情况下可能很笨拙

直接公开状态变量的主要原因是能够在这些字段上直接使用基本运算符。如果操作得当,可以提高可读性和方便性:例如,使用
+
添加复数,或使用
[]
访问键控集合。如果您使用的语法遵循传统惯例,那么这样做的好处可能会令人惊讶

问题在于运算符不是通用接口。只有一组非常特定的内置类型可以使用它们,这些类型只能以语言所期望的方式使用,并且不能定义任何新的类型。因此,一旦您使用原语定义了公共接口,您就锁定了自己使用该原语,并且只使用该原语(以及其他可以轻松转换到它的东西)。要使用其他任何东西,你必须在每次与它互动时围绕着它跳舞,从一个枯燥的角度来看,这会让你丧命:事物会很快变得非常脆弱

有些语言将运算符变成通用接口,但Java没有。这不是一个通用接口
public class Sculpture {
    public int weight = 0;
    public int price = 0;
}
public class Foo {
    public final int x;
    public final int y;
    public final Bar z;

    public Foo( int x, int y, Bar z) {
        this.x = x;
        this.y = y;
    }
}

public class Bar {
    public int age; //Oops this is not final, may be a mistake but still
    public Bar(int age) {
        this.age = age;
    }
}