«    »

Exposing Mutable Objects as Public Properties

I recently had an interesting design discussion with a coworker in which we discussed the pros and cons of exposing mutable objects as public properties of a class. This article provides my thoughts on the subject.

An immutable class (or object) is one whose state cannot be changed once the instance is constructed. Mutable objects do allow state changes, typically via setter methods.

Below is an example of such a mutable class:

import java.util.Calendar;

public class Order
{
  private Calendar date = Calendar.getInstance();

  public Calendar getDate() {
    return date;
  }

  public void setDate(Calendar calendar) {
    this.date = calendar;
  }
}

The public property date uses Calendar as its type. Calendar is itself a mutable class – it has methods such as setters that modify the state of the class. So what is the issue? Consider the following code:

  public static void exampleOne(Order order) {
    Calendar cal = order.getDate();
    // Is order past due?
    cal.add(Calendar.DAY_OF_YEAR, -10);
    if (cal.before(Calendar.getInstance())) {
      // Order past due logic...
    }
  }

Can you spot the problem? I must admit that I failed to notice anything wrong at first glance. Here is the same example instrumented with print statements. Can you predict what will happen when you run it?

import java.text.DateFormat;
import java.util.*;

public class ExampleTwo
{
  public static void exampleTwo(Order order) {
    print("Original order date = "
      + convertToText(order.getDate()));
    Calendar cal = order.getDate();

    // Is order past due?
    cal.add(Calendar.DAY_OF_YEAR, -10);
    if (cal.before(Calendar.getInstance())) {
      // Order past due logic...
    }
    print("Ending order date = "
      + convertToText(order.getDate()));
  }

  private static void print(String message) {
    System.out.println(message);
  }

  private static String convertToText(Calendar calendar) {
    return DateFormat.getDateInstance().format(
      new Date(calendar.getTimeInMillis()));
  }

  public static void main(String[] args) {
    Order order = new Order();
    exampleTwo(order);
  }
}

Below is the console output from executing this code:

Original order date = 8-Aug-2008
Ending order date = 29-Jul-2008

This clearly shows the problem: the date contained in the Order instance is changed within the exampleTwo method, which is likely incorrect behavior. The trap for a developer using the Order class (i.e. to write exampleTwo()) is that they would not necessarily consider or realize that changing the Calendar object returned by order.getDate() would change the value within the instance. The Order class is correct (no defects) but the implementation is unsafe because a mutable object is returned from the getter. How can this be addressed?

One solution for this particular example is to take the order-past-due check and turn it into a method on the Order class that does not change the date. While I would likely employ this solution, it unfortunately does not address the underlying issue. Even if the getDate() method is removed, the underlying problem can occur with the setDate() method. The following example demonstrates this:

import java.text.DateFormat;
import java.util.*;

public class ExampleThree
{
  public static void exampleThree() {
    Calendar calendar = Calendar.getInstance();
    Order firstOrder = new Order();
    Order secondOrder = new Order();
    firstOrder.setDate(calendar);

    calendar.add(Calendar.DAY_OF_YEAR, 10);
    secondOrder.setDate(calendar);

    print("First date = "
      + convertToText(firstOrder.getDate()));
    print("Second date = "
      + convertToText(secondOrder.getDate()));
  }

  private static void print(String message) {
    System.out.println(message);
  }

  private static String convertToText(Calendar calendar) {
    return DateFormat.getDateInstance().format(
      new Date(calendar.getTimeInMillis()));
  }

  public static void main(String[] args) {
    exampleThree();
  }
}

The console output is:

First date = 19-Aug-2008
Second date = 19-Aug-2008

The same instance of Calendar is added to both Order instances, so when the calendar's date value is changed for assignment to the secondOrder instance, it also changes within the firstOrder instance. This is likely incorrect behavior which occurs because the setter directly stores the provided mutable object. So we end up with two references to Calendar in each of the Order instances that point to the same instance. This is called aliasing.

By now you are perhaps convinced that getters and setters should never deal with mutable objects. It is unfortunately not that simple. Consider the following example:

public class Order
{
  private Customer customer;

  public Customer getCustomer() {
    return customer;
  }

  public void setCustomer(Customer customer) {
    this.customer = customer;
  }
}

public class Customer
{
  private String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

public class ExampleFour
{
  public static void exampleFour(Order order) {

    print("Original order customer name = "
      + order.getCustomer().getName());
    
    String newName = "New name";
    Customer customer = order.getCustomer();
    customer.setName(newName);

    print("Final order customer name = "
      + order.getCustomer().getName());
  }

  private static void print(String message) {
    System.out.println(message);
  }

  public static void main(String[] args) {
    Customer customer = new Customer();
    customer.setName("Starting name");
    Order order = new Order();
    order.setCustomer(customer);
    exampleFour(order);
  }
}

The console output is:

Original order customer name = Starting name
Final order customer name = New name

In this case it seems perfectly reasonable to update the customer's name and have this change remembered within the Order instance. Before I explain the difference between these scenarios, I will show one more example involving a collection property:

import java.util.*;

public class Order
{
  private Customer customer;

  public Customer getCustomer() {
    return customer;
  }

  public void setCustomer(Customer customer) {
    this.customer = customer;
  }
}

public class Customer
{
  private List orders = new ArrayList();

  public List getOrders() {
    return orders;
  }

  public void addOrder(Order order) {
    if (order == null) {
      return;
    }
    orders.add(order);
    order.setCustomer(this);
  }
}

In this case, calling customer.getOrders().add(order) might seem reasonable but is not correct as this will not invoke the extra logic in customer.addOrder(order) to call order.setCustomer().

The following table summarizes the above discussion.

Property Type Expect Modification of Original Object When Getter Result is Changed? Is Actual Behavior Correct?
Calendar No No
Customer Yes Yes
List Maybe No

Given that the code for the Calendar property compared to the Customer property is basically identical, why should we have different expectations of their behavior? The reason has to do with the nature of the two types. Calendar is a Value Object - a simple object whose equality is based on its value rather than its identity. Changing a calendar means ending up with a new date that is not equal to the original. Customer is a Reference Object – an object with business meaning whose identity is the basis for equality. Two customers can have the same values but be distinct. Changing a customer is just that – a change to that customer that should propagate throughout the system. For a fuller discussion of value objects see page 486 of the book Patterns of Enterprise Application Architecture by Martin Fowler. This discussion includes a short section on the risk of encountering aliasing defects if a value object is mutable and recommends that they be immutable.

I agree with this recommendation and I think it should be a design principle for classes: class properties that are value objects should be immutable. Many typical value object types in Java such as String and Long are already immutable, but we have already seen that others such as Calendar and List are not. What are the options in such circumstances? I will address this question in a future article.

The source code listed in this article is provided in the Java Examples project which can be downloaded from the Software page.

If you find this article helpful, please make a donation.

«    »