Standing on the shoulders (well, maybe feet) of giants
[ start | index | login or register ]
start > Value Object Motivation v2

Value Object Motivation v2

Created by Dirk Riehle. Last edited by Dirk Riehle, 2 years and 110 days ago. Viewed 734 times. #1
[edit] [rdf]
labels
attachments
Consider a financial services application for handling deposits and withdrawals to and from accounts. You are dealing with monetary amounts like ‘$42’ or ‘€107’. Let’s assume you need to maintain the balance for some account. It is tempting then to represent the balance using a currency symbol and a BigDecimal for the monetary amount.

public class Account {

protected char currency = '$'; protected BigDecimal balance = BigDecimal.ZERO; …

}

After a short consideration, you decide it is better to create a dedicated Money class to hold both the currency symbol and the monetary amount. The Money class will have methods for getting and setting the currency symbol, as well as methods for doing some arithmetic with the monetary amount, like adding or subtracting money.

public class Money {

public static final Money ZERO = new Money('$', BigDecimal.ZERO);

protected char currency = '$'; protected BigDecimal amount = BigDecimal.ZERO;

public Money(char myCurrency, BigDecimal myAmount) { currency = myCurrency; amount = myAmount; }

public synchronized void add(Money otherMoney) { addAmount(otherMoney.getAmount()); }

public synchronized void addAmount(BigDecimal otherAmount) { amount = amount.add(otherAmount); } … }

An Account class then makes use of Money objects.

public class Account {

protected Money balance = Money.ZERO; …

public synchronized void deposit(Money moreMoney) { balance.add(moreMoney); }

public synchronized void withdraw(Money lessMoney) { balance.subtract(lessMoney); }

… }

So far so good. You go ahead and use the Account and Money classes for implementing a money transfer method on a ‘financial application server’ class. (We are ignoring transaction and failure handling to keep it simple.)

public class FinAppServer {
	public static synchronized void transfer(Money money, Account from, Account to) {
		from.withdraw(money);
		to.deposit(money);
	}
	…
}

A simple unit test checks this implementation:

public void testTransfer() {
		Account from = new Account();
		Account to = new Account();
		from.deposit(new Money('$', 42));

FinAppServer.transfer(new Money('$', 3.14), from, to);

BigDecimal fromAmount = from.getBalance().getAmount(); assert(fromAmount.doubleValue() == (42 - 3.14));

BigDecimal toAmount = to.getBalance().getAmount(); assert(toAmount.doubleValue() == 3.14); }

If you are like me, you’ll be surprised to learn that this test case fails. Both from and to accounts hold a ‘$42’ balance, which is wrong. (The from account should have an amount value of 42 – 3.14 and the to account should have an amount value of 3.14.)

You probably quickly figure out what’s going on: Both accounts started out with the ZERO Money object as its bal-ance; the initial deposit of ‘$42’ to the from account also set the to account balance to ‘$42’, after all, it is the same Money object. The subsequent subtraction (in the withdraw method) and addition (in the deposit method) of ‘$3.14’ evened out the value change in the balance object. Since both accounts are holding the same object as their balance, both will display ‘$42’.

This is the aliasing or side-effect problem. Here, it may appear trivial to avoid, but it easily gets more complicated: The side-effects of changing an object that is referenced from another place are not always easy to comprehend. If we can avoid such nasty surprises, we will be well served.

To quickly fix the bug, you change the balance initialization code of the Account class:

protected Money balance = new Money('$', 0.0);

The testTransfer test case works nicely now, and you decide to enhance the money transfer functionality. Specifically, you want a transfer to take place only if the account being withdrawn from does not fall below $0. So you rewrite the transfer method.

public static synchronized void transfer(Money money, Account from, Account to) {
		Money fromBalance = from.getBalance();
		fromBalance.subtract(money);
		if (fromBalance.isLowerThan(Money.ZERO)) {
			throw new RuntimeException("Insufficient funds!");
		}

from.withdraw(money); to.deposit(money); }

This time we don’t have to write a test case to see that this won’t work. The subtraction in line 3 not only changes the local variable fromBalance but also the value of the underlying balance of the from account. If we were to run a transfer where the from account had enough money, it would find the transfer amount to be withdrawn twice. Firstly, from the test to check whether the withdrawal will not overdraw the account, and secondly, from the real withdraw method call.

One possible solution is to make the Account object hand out only copies of its balance Money object. This way, no client could ever change the balance of an account without going through its regular methods. This also leads to a lot of copies if you check balances frequently. Many of these copies may never be changed and are likely to be discarded quickly. Basically, you are buying protection by copying eagerly without knowing whether you’ll ever need that protection.

It is possible to pay the price of protection only when it is needed. For this, you need to make each Money object immutable, which means you make it return a new object every time where otherwise it would change its state. Let’s look at the addAmount method to see what this means:

public Money addAmount(BigDecimal otherAmount) {
	BigDecimal newAmount = amount.add(otherAmount);
	return new Money(currency, newAmount);
}

public Money subtractAmount(BigDecimal otherAmount) { BigDecimal newAmount = amount.subtract(otherAmount); return new Money(currency, newAmount); }

Rather than changing its internal fields, a Money object returns a new Money instance that holds the desired values. This way, the original Money object doesn’t change. Read-only methods won’t require a new object. You only create a new object if a state change occurs.

As a consequence, you have to change the way you program with Money objects. For example, the deposit method of the Account class now looks like this:

public synchronized void deposit(Money moreMoney) {
		balance = balance.add(moreMoney);
	}

Contrast this code with how it would have looked like if you had used a double instead of our Money class:

public synchronized void deposit(double moreMoney) {
		balance = balance + moreMoney;
	}

The code is structurally equivalent. Now that they are immutable, the code for dealing with Money objects looks like the code for dealing with built-in value types (data types). The Money class we have designed behaves like a value type. Its instances are called value objects, and the class itself is an application of the Value Object pattern.

Next

one comment (by Dirk Riehle) | post comment
Copyright (©) 2006 by Dirk Riehle or the respective authors. All rights reserved.