Domanda

I was wondering... what is considered the best practice to instantiate blank value objects? (in Java)

E.g. Assume we have some value object class Foo, that could be blank.

Would creating methods like these be a clean solution? (And perhaps adding in a "Blankable" interface or something).

interface Blankable {
    public Foo blankInstance();
    public boolean isBlank();
}

class Foo implements Blankable {
    public Foo blankInstance();
    public boolean isBlank();
}

Foo foo = something();
if (foo.isBlank()) // Tell the user

Or would something more simple like this be better?

class Foo {
    public static Foo BLANK = /* Some constructor */;
}

Foo foo = something();
if (foo == Foo.BLANK) // Tell the user

I am leaning toward the first example, but I am still not sure if it is a widely accepted technique.

I feel a bit resistant to (but still open to) using the Guava Optional< T> class because it seems like a work-around solution in this circumstance.

È stato utile?

Soluzione

A solution such as Option<T> is not a so bad idea. In fact, it has been successfully introduced in Haskell as a Maybe (but I don't know if this is the first time this solution has been used). Maybe monads are common in Haskell programs. Since, other languages have adopted the same system. For instance, Scala has an Option type.

If you don't want to become dependent of Guava, you could use Java 8, which introduces the Optional class.

However, Option/Maybe/… types are more relevant in a functional environment, because what you typically want to do is to get a behaviour/a property from the element if it really exists, and to get nothing or a default behaviour/property otherwise. So, a typical use of Options consists in

  • applying a function A->B on the Option[A] in order to get an Option[B];
  • switching your code on the basis of the real content of the Option (for instance, by using Pattern matching if the language supports it and if Option has two subtypes, or by using method overload);
  • or filtering the Options that represent (non-)existing elements.

As an alternative, you could use the Null Object Pattern, in which you have an abstract class MyClass and two concrete subclasses: MyRealClass and MyNullClass. You manipulate instances of MyClass, but generate instances of MyRealClass (if the element is really existing) or MyNullClass (if the element doesn't exist). MyNullClass contains the default behaviours/properties. If the null objects are stateless (which is typically the case), one could cache them, or even make them singletons.

This pattern is described in [Fowler].

[Fowler] Martin Fowler, Kent Beck, Refactoring: Improving the Design of Existing Code.

Altri suggerimenti

The whole point of a value object is that equality isn't based on identity; you could very well have two distinct blank objects. In fact, whether all blank objects are the same or different objects should be an implementation detail. For those reasons, the second approach is not good. If you look at the Java 8 APIs, there's a notion of a "value-based class" with the following properties:

  • are final and immutable (though may contain references to mutable objects);
  • have implementations of equals, hashCode, and toString which are computed solely from the instance's state and not from its identity or the state of any other object or variable;
  • make no use of identity-sensitive operations such as reference equality (==) between instances, identity hash code of instances, or synchronization on an instances's intrinsic lock;
  • are considered equal solely based on equals(), not based on reference equality (==);
  • do not have accessible constructors, but are instead instantiated through factory methods which make no committment as to the identity of returned instances;
  • are freely substitutable when equal, meaning that interchanging any two instances x and y that are equal according to equals() in any computation or method invocation should produce no visible change in behavior.

The documentation further adds:

A program may produce unpredictable results if it attempts to distinguish two references to equal values of a value-based class, whether directly via reference equality or indirectly via an appeal to synchronization, identity hashing, serialization, or any other identity-sensitive mechanism. Use of such identity-sensitive operations on instances of value-based classes may have unpredictable effects and should be avoided.

There's no way to disable the == operator in Java so that warning is the best you can do.

Thus, your first approach is correct, with the caveat that foo1.equals(foo2) should always be true when foo1.isBlank() and foo2.isBlank() even if foo1 != foo2.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
scroll top