Is it possible to expose the state of an immutable object?

Having recently looked at the concept of immutable objects, I would like to learn the best practices for controlling access to the state. Despite the fact that the object-oriented part of my brain makes me want to restrain fear in front of the eyes of public members, I do not see technical problems with something like this:

public class Foo { public final int x; public final int y; public Foo( int x, int y) { this.x = x; this.y = y; } } 

I would feel more comfortable declaring the fields as private and providing getter methods for everyone, but this seems too complicated when the state is explicitly open.

What is the best practice for providing access to the state of an immutable object?

+63
java immutability value-objects
Feb 20 '14 at 14:29
source share
13 answers

It all depends on how you intend to use the object. Public fields are not inherently evil, it’s just bad that everything is public. For example, the java.awt.Point class makes its x and y fields public, and they are not even final. Your example seems like a fine use of public fields, but again, you may not need to disclose all the internal fields of another immutable object. There is no catch-all rule.

+62
Feb 20 '14 at 2:33
source share

I thought about this in the past, but I usually end up making variables private and using getters and setters, so later on I will still be able to make changes to the implementation while maintaining the same interface.

It reminded me of what I recently read in Robert C. Martin's Clean Code. In chapter 6, he gives a slightly different perspective. For example, on page 95 he claims

"Objects hide their data behind abstractions and reveal the functions that work with this data. The data structure reveals their data and does not have significant functions."

And on page 100:

Quasi-encapsulation of beans seems to make some OO purists better, but usually does not provide any other benefit.

Based on the sample code, the Foo class will be a data structure. Therefore, based on what I understood from the discussion in Pure Code (this is more than just the two quotes I gave), the purpose of the class is to expose data, not functionality, but the presence of getters and setters, probably not very good.

Again, in my experience, I usually went ahead and used the "bean" private data approach with getters and setters. But then again, no one ever asked me to write a book on how to write better code, so perhaps Martin has something to say.

+31
Feb 20 '14 at 2:56
source share

If your object has local sufficient use, you do not need problems with changing the APIs for it in the future, there is no need to use getters on top of instance variables. But this is a general subject, not related to immutable objects.

The advantage of using getters comes from one additional layer of indirection, which can come in handy if you are developing an object that will be widely used and whose usefulness will spread into the unpredictable future.

+11
Feb 20 '14 at 2:34
source share

Regardless of immutability, you are still exposing implementations of this class. At some point, you will want to change the implementation (or perhaps make various derivations, for example, using the Point example, you might want a similar Point class to use polar coordinates), and your client code will be exposed to this.

The above template may be useful, but I usually limited it to very localized instances (for example, by passing tuples of information around - I usually find that objects of seemingly unrelated information, however, are either bad encapsulations, or the information is connected, and my tuple turns to a full object)

+6
Feb 20 '14 at 2:36
source share

It is important to remember that function calls provide a universal interface. Any object can interact with other objects using function calls. All you have to do is determine the correct signatures and leave. The only catch is that you have to interact exclusively through these function calls, which often work well, but can be inconvenient in some cases.

The main reason for directly displaying state variables is the ability to use primitive operators directly in these fields. If this is done well, it can increase readability and usability: for example, adding complex numbers with + or accessing the collection with the key using [] . The benefits of this can be surprising if your use of the syntax follows traditional conventions.

The trick is that operators are not a universal interface. Only they can be used only for a specific set of built-in types, they can only be used as required by the language, and you cannot define new ones. So, as soon as you defined your public interface using primitives, you locked yourself in using this primitive and only that primitive (and other things that can easily be attributed to it). To use anything else, you have to dance around this primitive every time you communicate with him, and this kills you from a harsh point of view: everything can become very fragile very quickly.

Some languages ​​turn statements into a universal interface, but Java does not. This is not an accusation of Java: its developers decided to deliberately not include operator overloading, and they had good reasons for this. Even when you work with objects that seem to blend well with the traditional meanings of the operators, making them work in a way that actually makes sense, can be surprisingly nuanced, and if you don't completely nail it, you're going to pay for it later. It is often much easier to make a function-based interface understandable and usable than going through this process, and you often even get better results than if you were using operators.

However, there were compromises in this decision. There are times when the operator interface really works better than function-based, but without operator overloading, this parameter is simply not available. An attempt to force shoehorn operators to block you in any case in some design decisions that you probably really don't want them to be set in stone. The Java designers thought this compromise was worth it, and they might even be right. But such decisions do not come without any loss, and such a situation arises where the defeat falls.

In short, the problem is not to expose your implementation as such. The problem is to block yourself in this implementation.

+5
Feb 20 '14 at 18:17
source share

In fact, it breaks the encapsulation to expose any property of the object in any way - each property is a part of the implementation. Just because everyone does it, doesn't do it right. Using accessories and mutators (getters and setters) does not make it better. Rather, CQRS patterns should be used to maintain encapsulation.

+4
Feb 20 '14 at 21:11
source share

I know only one support to have getters for the final properties. This is the case when you want to access properties through an interface.

  public interface Point { int getX(); int getY(); } public class Foo implements Point {...} public class Foo2 implements Point {...} 

Otherwise, the public end fields are fine.

+3
Feb 20 '14 at 14:43
source share

The class you developed should be good in its current incarnation. Usually problems arise when someone tries to change this class or inherit it.

For example, after looking at the code, someone thinks about adding another instance of a member variable of the Bar class.

 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; } } 

In the above code, the instance of Bar cannot be modified, but from the outside, anyone can update the value of Bar.age.

Best practice is to mark all fields as private, have fields for fields. If you are returning an object or collection, be sure to return an immutable version.

Immunity is necessary for parallel programming.

+3
Feb 26 '14 at 1:29
source share

An object with public end fields that are loaded from the general parameters of the constructor effectively portrays itself as a simple data holder. Although such data holders are not particularly "OOP-ish", they are useful for resolving a single field, variable, parameter, or return value to encapsulate multiple values. If the goal of a type is to simply glue several values ​​together, such a data holder is often the best representation within a framework without real values.

Consider the question of what you would like to accomplish if any Foo method wants to provide the caller with a Point3d that encapsulates "X = 5, Y = 23, Z = 57", and, as it happens, a link to a Point3d , where X = 5, Y = 23, and Z = 57. If the Foo thing is known as a simple immutable data holder, then Foo should just give the caller a link to it. If, however, it can be something else (for example, it may contain additional information outside of X, Y and Z), then Foo should create a new simple data holder containing "X = 5, Y = 23, Z = 57 "and give the caller refers to this.

With Point3d , we seal and publish its contents, because public end fields will imply that methods like Foo can consider it a simple immutable data holder and can safely exchange references to its instances. If there is code that makes such assumptions, it may be difficult or impossible to change Point3d as anything other than a simple immutable data holder without breaking that code. On the other hand, the code that Point3d assumes is a simple immutable data holder can be much simpler and more efficient than code that must deal with the possibility that it is something else.

+2
Feb 21 '14 at 18:30
source share

This style is very different from Scala, but there is a crucial difference between these languages: Scala follows the principle of uniform access , but Java does not. This means that your design is beautiful until your class changes, but it can break in several ways when you need to adapt your functionality:

  • you need to extract the interface or superclass (for example, your class is complex numbers, and you also want to have a sister class with a polar representation of the coordinates)
  • you need to inherit from your class, and the information becomes redundant (for example, x can be calculated from the additional data of the subclass)
  • you need to check the constraints (for example, x must be non-negative for some reason)

Also note that you cannot use this style for mutable elements (e.g. the infamous java.util.Date ). Only with getters can you make a protective copy or change the view (for example, save Date information as long )

+2
Feb 21 '14 at 22:03
source share

I use many constructs very similar to the one you asked in the question, sometimes there are things that can be better modeled using (sometimes inmutable) data-strcuture than with the class.

It all depends if you model an object, the object defined by its behavior, in this case, never exposes the internal properties. In other cases, you are modeling a data structure, and java does not have a special construction for data structures, it is great to use a class and publish all the properties, and if you want the immutable final and public course.

For example, Robert Martin has one chapter about this in the wonderful book, Clean Code, which should be read, in my opinion.

+1
Feb 20 '14 at
source share

In cases where the sole purpose is to bind the two values ​​to each other under a meaningful name, you might even consider skipping the definition of any constructors and save the variables:

 public class Sculpture { public int weight = 0; public int price = 0; } 

This has the advantage of minimizing the risk of confusing the order of parameters when creating an instance of the class. Limited variability, if necessary, can be achieved if the entire container is under private control.

+1
Feb 21 '14 at 8:35
source share

I just want to reflect the reflection :

 Foo foo = new Foo(0, 1); // x=0, y=1 Field fieldX = Foo.class.getField("x"); fieldX.setAccessible(true); fieldX.set(foo, 5); System.out.println(foo.x); // 5! 

So is Foo still unchanged? :)

0
Feb 20 '14 at 14:45
source share



All Articles