As an informed developer or architect you will probably know what makes functional languages like Scheme, F# or Erlang so powerful: They share the inherent effort of their creators to make the creation of complex behavior easy (by combining simple elements) and the introduction of undesired side-effects hard. But achieving the same in OO languages like Java it is just as easy if you follow some simple principles (or guidelines, if you do not like principles). In this post I will scratch the problem of unintended side-effects because in my experience it receives not enough attention in practice. After all there are always serious integration and distribution issues to solve, so why care about making simple applications sound? Just kidding.
Here is the key idea to solve the issue of unrecognized side-effects: Instead of creating aliases to the same mutable storage cell, all one needs to do instead is create new or copying existing objects, leaving existing ones unmodified. This principle is inherent to the functional programming style. Like the number two denotes the same concept, regardless of who thinks about it or writes it down on a piece of paper, the same is true of numbers or even larger structures in the functional paradigm. If you add an element to a list, you are creating a new one (using cells, cons, car and cdr in LISP), leaving the existing one intact. Because you cannot modify the lists’ elements the old list will exist until it goes out of scope and eventually gets garbage collected.
We can and should use this method in Java often and happily. The usage of final fields and correctly overloaded equals(..) and hashCode() form the basis of any sound, object-oriented domain-model in Java. The ValueObject design pattern was created and is perfect for creating the basic elements of the domain-model. A ValueObject is immutable, thus only publishing methods that create or return another immutable instance but never modify an existing on. It is small and its equality is usually based on the comparison of each of its parts. Date, Money, Distance or (musical) Pitch are common examples of ValueObjects. Developers can safely use and share ValueObjects and focus on desired side effects of the commands of reference objects aka entities. As a bonus we get multi-threading safety (see Java Memory Model).
Let’s take this idea a step further. Think of a typical anaemic object (an entity this time) having a list of addresses as part of its inner state. Using Java Beans style getters and setters will almost certainly get you into problems with undesired side-effects. There will always be someone in the team (often even a programming pair) who unwillingly does not copy a list before modification. Maybe just in a special case. In my career this was once the case in a net of Controller-like classes responsible for both redrawing the screen and creating print output. A user complained, after printing a page there would be a runtime exception when the next screen update happened. Guess how efficient I was tracking down that bug – without any unit tests of course (UI code is rarely well tested in practice). Well, you probably guessed this problem was created by a list, that was not copied but referenced from the print logic, modified before printing and so created inconsistent control data. I thanked the Eclipse debugger developers twice this day.
What does this indicate for designing domain-model classes? My advice: Add getters and setters only if required by some framework, else use an intention-revealing interface style like proposed by NakedObjects / Apache Isis. It is really easy to make an interface intention-revealing:
// Should do a deep copy if addresses are mutable.
Expose the methods you need – you do not need to conform the Java Bean convention (which was designed for tools of the 1990s anyway). Note how using domain-related, readable method names makes the code abide to the Law-of-Demeter and adhere to the OO principle “Tell, don’t ask” [Sharp1997]. If you are modeling a bidirectional 1:n association you may easily apply the Mutual Registration Pattern to attach and detach elements to and from their parent.
What I find quite interesting is how creating intention-revealing interfaces using terms from a domains Ubiquitous Language relates to the rename / redefine language constructs of the EIFFEL language. EIFFEL has a well designed approach to multiple inheritance, that is conceptually much more sound than in C++ for example. While C++ uses non-satisfying precedence rules in case of name clashes, EIFFEL allows the creator of a subclass to decide which features of the base classes are exposed by which name. In this example, the add method of java.util.(Array)List could be renamed to addAddress and in a second step redefined to a void method. java.util.List.add(..) returns a boolean which does not conform to the Command-Query Separation principle. Note that redefinition of a method in Java means creating a trivial delegate method.
I must say that Meyer’s use of inheritance is one of the few positions that I do not share. In a forthcoming post I will discuss how inheritance relates to composition and delegation. In my current understanding and from the point of view of the designer of an object-model inheritance seems to be best explained as a special form of delegation. Anyway, EIFFELs approach on multiple inheritance is much more useful for designing domain-models than that of C++ and fits very well within the language, so it is very worth to mention.
After talking so much about side-effects let me clarify, that because we are in the imperative programming paradigm there are also wanted side-effects. Call them mutators, setters or commands, what you want to achieve in object-oriented programming is come close the real world with a clever set of classes that have as much in common with the application domain as possible. If there is mutable state in an object of the application domain, it should be modeled as mutable state in our little (or not so little) computer world. If the the state of an object should not be mutable, think of which demand once made Xerox so successful: Creating copies!
Meyer, Bertrand: “Overloading vs.Objectechnology”, Journal of Object-Oriented Programming, October/November 2001, p. 3-7.
Meyer, Bertrand: “Eiffel: The Language, Prentice Hall, Englewood Cliffs”, NY., 1992.
Sharp, Alan: “Smalltalk By Example”, McGraw-Hill, 1997.