Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
November 4, 2013 10:00 pm GMT

Money Pattern: The Right Way to Represent Value-Unit Pairs

The Money Pattern, defined by Martin Fowler and published in Patterns of Enterprise Application Architecture, is a great way to represent value-unit pairs. It is called Money Pattern because it emerged in a financial context and we will illustrate its use mainly in this context using PHP.


A PayPal Like Account

I have no idea how PayPal is implemented, but I think it is a good idea to take its functionality as an example. For example my PayPal account has two currencies: US Dollars and Euro. It keeps the two values separated, but I can receive in any currency, I can see my total amount in any of the two currencies and I can extract in any of the two. For the sake of this example, imagine that we extract in any of the currencies and automatic conversion is done if the balance of that specific currency is less than what we want to transfer but there is enough money in the other currency. Also, we will limit the example to only two currencies.


Getting an Account

If I would to create and use an Account object, I would like to initialize it with an account number.

function testItCanCrateANewAccount() {$this->assertInstanceOf("Account", new Account(123));}

This will obviously fail because we have no Account class, yet.

class Account {}

Well, writing that in a new “Account.php” file and requiring it in the test made it pass. However this is all just to make ourselves comfortable with the idea. Next, I am thinking of getting the account’s id.

function testItCanCrateANewAccountWithId() {$this->assertEquals(123, (new Account(123))->getId());}

I actually changed the previous test into this one. There is no reason to keep the first one. It lived it’s life, meaning it forced me to think about the Account class and actually create it. We can move on.

class Account {private $id;function __construct($id) {$this->id = $id;}public function getId() {return $this->id;}}

Test is passing and Account is starting to look like a real class.


Currencies

Based on our PayPal analogy we may want to define a primary and a secondary currency for our account.

private $account;protected function setUp() {$this->account = new Account(123);}[...]function testItCanHavePrimaryAndSecondaryCurrencies() {$this->account->setPrimaryCurrency("EUR");$this->account->setSecondaryCurrency('USD');$this->assertEquals(array('primary' => 'EUR', 'secondary' => 'USD'), $this->account->getCurrencies());}

Now, that test will force us to write this code.

class Account {private $id;private $primaryCurrency;private $secondaryCurrency;[...]function setPrimaryCurrency($currency) {$this->primaryCurrency = $currency;}function setSecondaryCurrency($currency) {$this->secondaryCurrency = $currency;}function getCurrencies() {return array('primary' => $this->primaryCurrency, 'secondary' => $this->secondaryCurrency);}}

For the time being we are keeping currency as a simple string. This may change in the future, but we are not there yet.


Gimme the Money

There are endless reasons why not to represent money as a simple value. Floating point calculations? Anyone? What about currency fractionals? Should we have 10, 100 or 1000 cents in some exotic currency? Well, this is another problem we will have to avoid. What about allocating indivisible cents?

There are just too many and exotic problems when working with money to write them down in code, so we will go directly with the solution, the Money Pattern. This is a quite simple pattern with great advantages and use cases far out of the financial domain. Whenever you have to represent a value-unit pair you should probably use this pattern.

UML

The Money Pattern is just basically a class encapsulating an amount and currency. Then it defines all the mathematical operations on the value with respect to the currency. “allocate()” is a special function to distribute a specific amount of money between two or more recipients.

So, as a user of Money I would like to be able to do this in a test:

class MoneyTest extends PHPUnit_Framework_TestCase {function testWeCanCreateAMoneyObject() {$money = new Money(100, Currency::USD());}}

That won’t work yet. We need both Money and Currency. Even more, we need Currency before Money. This will be a simple class, so I will skip testing it right now. I am pretty sure the IDE can generate most of the code for me.

class Currency {private $centFactor;private $stringRepresentation;private function __construct($centFactor, $stringRepresentation) {$this->centFactor = $centFactor;$this->stringRepresentation = $stringRepresentation;}public function getCentFactor() {return $this->centFactor;}function getStringRepresentation() {return $this->stringRepresentation;}static function USD() {return new self(100, 'USD');}static function EUR() {return new self(100, 'EUR');}}

That is enough for our example. We have two static functions for USD and EUR. In a real application we would probably have a general constructor with parameter and load all the currencies from a database table or, even better, from a text file.

Include the two new files in the test:

require_once '../Currency.php';require_once '../Money.php';class MoneyTest extends PHPUnit_Framework_TestCase {function testWeCanCreateAMoneyObject() {$money = new Money(100, Currency::USD());}}

This still fails, but at least it can find Currency now. We continue with a minimal Money implementation. A little bit more than what this test strictly requires since it is, again, mostly auto-generated code.

class Money {private $amount;private $currency;function __construct($amount, Currency $currency) {$this->amount = $amount;$this->currency = $currency;}}

Please note, we enforce the type Currency for the second parameter in our constructor. This is a nice way to avoid our clients sending in junk as currency.


Comparing Money

The first thing that came into my mind after having the minimal object up and running was that I will have to compare money objects somehow. Then I remembered that PHP is quite smart when it comes to comparing object, so I wrote this test.

function testItCanTellTwoMoneyObjectAreEqual() {$m1 = new Money(100, Currency::USD());$m2 = new Money(100, Currency::USD());$this->assertEquals($m1,$m2);$this->assertTrue($m1 == $m2);}

Well, that actually passes. The “assertEquals” function can compare the two objects and even the built-in equality condition from PHP “==” is telling me what I expect. Nice.

But, what about if we are interested in one being bigger than the other? To my even greater surprise the following test also passes without any problems.

function testOneMoneyIsBiggerThanTheOther() {$m1 = new Money(200, Currency::USD());$m2 = new Money(100, Currency::USD());$this->assertGreaterThan($m2, $m1);$this->assertTrue($m1 > $m2);}

Which leads us to…

function testOneMoneyIsLessThanTheOther() {$m1 = new Money(100, Currency::USD());$m2 = new Money(200, Currency::USD());$this->assertLessThan($m2, $m1);$this->assertTrue($m1 < $m2);}

… a test that passes immediately.


Plus, Minus, Multiply

Seeing so much PHP magic actually working with comparison, I could not resist to try this one.

function testTwoMoneyObjectsCanBeAdded() {$m1 = new Money(100, Currency::USD());$m2 = new Money(200, Currency::USD());$sum = new Money(300, Currency::USD());$this->assertEquals($sum, $m1 + $m2);}

Which fails and says:

Object of class Money could not be converted to int

Hmm. That sounds pretty obvious. At this point we have to take a decision. It is possible to continue this exercise with even more PHP magic but this approach will at some point transform this tutorial into a PHP cheatsheet instead of a design patter. So, let’s take the decision to implement the actual methods to add, subtract and multiply money objects.

function testTwoMoneyObjectsCanBeAdded() {$m1 = new Money(100, Currency::USD());$m2 = new Money(200, Currency::USD());$sum = new Money(300, Currency::USD());$this->assertEquals($sum, $m1->add($m2));}

This fails as well, but with an error telling us there is no “add” method on Money.

public function getAmount() {return $this->amount;}function add($other) {return new Money($this->amount + $other->getAmount(), $this->currency);}

To sum up two Money objects we need a way to retrieve the amount of the object we pass in as the argument. I prefer to write a getter, but setting the class variable to be public would also be an acceptable solution. But what if we want to add Dollars to Euros?

/** * @expectedException Exception * @expectedExceptionMessage Both Moneys must be of same currency */function testItThrowsExceptionIfWeTryToAddTwoMoneysWithDifferentCurrency() {$m1 = new Money(100, Currency::USD());$m2 = new Money(100, Currency::EUR());$m1->add($m2);}

There are several ways to deal with operations on Money objects with different currencies. We will throw an exception and expect it in the test. Alternatively we could implement a currency conversion mechanism in our application, call it, convert both Money objects into some default currency and compare them. Or if we would have a more sophisticated currency conversion algorithm we could always convert from one to another and compare in that converted currency. The thing is, that when conversion comes in effect, conversion fees have to be considered and things are getting quite complicated. So let’s just throw that exception and move on.

public function getCurrency() {return $this->currency;}function add(Money $other) {$this->ensureSameCurrencyWith($other);return new Money($this->amount + $other->getAmount(), $this->currency);}private function ensureSameCurrencyWith(Money $other) {if ($this->currency != $other->getCurrency())throw new Exception("Both Moneys must be of same currency");}

That is better. We do a check to see if the currencies are different and throw an exception. I already wrote it as a separate private method because I know we will need it in the other mathematical operations also.

Subtraction and multiplication are very similar to addition so here is the code and you can find the tests in the attached source code.

function subtract(Money $other) {$this->ensureSameCurrencyWith($other);if ($other > $this)throw new Exception("Subtracted money is more than what we have");return new Money($this->amount - $other->getAmount(), $this->currency);}function multiplyBy($multiplier, $roundMethod = PHP_ROUND_HALF_UP) {$product = round($this->amount * $multiplier, 0, $roundMethod);return new Money($product, $this->currency);}

With subtraction we have to make sure we have enough money and with multiplication we must take actions to round things up or down so that division (multiplication with numbers less than 1) will not produce “half cents”. We keep our amount in cents, the lowest possible factor of the currency. We can not divide it more.


Introducing Currency to Our Account

We have an almost complete Money and Currency. It is time to introduce these objects to Account. We will start with Currency, and change our tests accordingly.

function testItCanHavePrimaryAndSecondaryCurrencies() {$this->account->setPrimaryCurrency(Currency::EUR());$this->account->setSecondaryCurrency(Currency::USD());$this->assertEquals(array('primary' => Currency::EUR(), 'secondary' => Currency::USD()), $this->account->getCurrencies());}

Because of the dynamic typing nature of PHP this test passes without any problems. However I would like to force the methods in Account to use Currency objects and do not accept anything else. This is not mandatory but I find these kind of type hintings extremely useful when someone has to understand our code.

function setPrimaryCurrency(Currency $currency) {$this->primaryCurrency = $currency;}function setSecondaryCurrency(Currency $currency) {$this->secondaryCurrency = $currency;}

Now it is obvious to anyone reading this code first time that Account works with Currency.


Introducing Money to Our Account

The two basic actions any account must provide is: deposit – meaning adding of money to an account – and withdraw – meaning removing money from an account. Deposition has a source and withdrawal has a destination other than our current account. We will not enter into details about how to implement these transactions, we will only concentrate on implementing the effects these have on our account. So, we can imagine a test like this for deposition.

function testAccountCanDepositMoney() {$this->account->setPrimaryCurrency(Currency::EUR());$money = new Money(100, Currency::EUR()); //That's 1 EURO$this->account->deposit($money);$this->assertEquals($money, $this->account->getPrimaryBalance());}

This will force us to write quite a lot of implementation.

class Account {private $id;private $primaryCurrency;private $secondaryCurrency;private $secondaryBalance;private $primaryBalance;function getSecondaryBalance() {return $this->secondaryBalance;}function getPrimaryBalance() {return $this->primaryBalance;}function __construct($id) {$this->id = $id;}[...]function deposit(Money $money) {$this->primaryCurrency == $money->getCurrency() ? $this->primaryBalance = $money : $this->secondaryBalance = $money;}}

OK, OK. I know, I wrote more than absolutely necessary production code. But I don’t want to bore you to death with baby-steps and I am also fairly sure the code for secondaryBalance will work correctly. It was almost entirely generate by the IDE. I will even skip testing it. While this code makes our test pass, we have to ask ourselves what happens when we do subsequent depositions? We want our money to be added to the previous balance.

function testSubsequentDepositsAddUpTheMoney() {$this->account->setPrimaryCurrency(Currency::EUR());$money = new Money(100, Currency::EUR()); //That's 1 EURO$this->account->deposit($money); //One euro in the account$this->account->deposit($money); //Two euros in the account$this->assertEquals($money->multiplyBy(2), $this->account->getPrimaryBalance());}

Well, that fails. So we have to update our production code.

function deposit(Money $money) {if ($this->primaryCurrency == $money->getCurrency()){$this->primaryBalance = $this->primaryBalance ? : new Money(0, $this->primaryCurrency);$this->primaryBalance = $this->primaryBalance->add($money);}else {$this->secondaryBalance = $this->secondaryBalance ? : new Money(0, $this->secondaryCurrency);$this->secondaryBalance = $this->secondaryBalance->add($money);}}

This is much better. We are probably done with the deposit method and we can continue with withdrawal.

function testAccountCanWithdrawMoneyOfSameCurrency() {$this->account->setPrimaryCurrency(Currency::EUR());$money = new Money(100, Currency::EUR()); //That's 1 EURO$this->account->deposit($money);$this->account->withdraw(new Money(70, Currency::EUR()));$this->assertEquals(new Money(30, Currency::EUR()), $this->account->getPrimaryBalance());}

This is a simple test for the happy path. The solution is simple, also.

function withdraw(Money $money) {$this->primaryCurrency == $money->getCurrency() ?$this->primaryBalance = $this->primaryBalance->subtract($money) :$this->secondaryBalance = $this->secondaryBalance->subtract($money);}

Well, that works, but what if we want to use a Currency that is not in our account? We should throw an Excpetion for that.

/** * @expectedException Exception * @expectedExceptionMessage This account has no currency USD */function testThrowsExceptionForInexistentCurrencyOnWithdrawal() {$this->account->setPrimaryCurrency(Currency::EUR());$money = new Money(100, Currency::EUR()); //That's 1 EURO$this->account->deposit($money);$this->account->withdraw(new Money(70, Currency::USD()));}

That will force us to check our currencies.

function withdraw(Money $money) {$this->validateCurrencyFor($money);$this->primaryCurrency == $money->getCurrency() ?$this->primaryBalance = $this->primaryBalance->subtract($money) :$this->secondaryBalance = $this->secondaryBalance->subtract($money);}private function validateCurrencyFor(Money $money) {if (!in_array($money->getCurrency(), $this->getCurrencies()))throw new Exception(sprintf('This account has no currency %s',$money->getCurrency()->getStringRepresentation()));}

But what if we want to withdraw more than we have? That case was already addressed when we implemented subtract on Money. Here is the test that proves it.

/** * @expectedException Exception * @expectedExceptionMessage Subtracted money is more than what we have */function testItThrowsExceptionIfWeTryToSubtractMoreMoneyThanWeHave() {$this->account->setPrimaryCurrency(Currency::EUR());$money = new Money(100, Currency::EUR()); //That's 1 EURO$this->account->deposit($money);$this->account->withdraw(new Money(150, Currency::EUR()));}

Dealing With Withdrawal and Exchange

One of the more difficult things to deal with when we are working with multiple currencies is exchanging between them. The beauty of this design pattern is that it allows us to somewhat simplify this problem by isolating and encapsulating it in it’s own class. While the logic in an Exchange class may be very sophisticated, its use becomes much easier. For the sake of this tutorial let’s imagine we have a very basic Exchange logic only. 1 EUR = 1.5 USD.

class Exchange {function convert(Money $money, Currency $toCurrency) {if ($toCurrency == Currency::EUR() && $money->getCurrency() == Currency::USD())return new Money($money->multiplyBy(0.67)->getAmount(), $toCurrency);if ($toCurrency == Currency::USD() && $money->getCurrency() == Currency::EUR())return new Money($money->multiplyBy(1.5)->getAmount(), $toCurrency);return $money;}}

If we convert from EUR to USD we multiply the value by 1.5, if we convert from USD to EUR we divide the value by 1.5, otherwise we presume we convert from the same currency to the same one so we do nothing and just return the money. Of course, in reality this would be a much more complicated class.

Now, having an Exchange class, Account can take different decisions when we want to withdraw Money in a currency but we do not hove enough in that specific currency. Here is a test that better exemplifies it.

function testItConvertsMoneyFromTheOtherCurrencyWhenWeDoNotHaveEnoughInTheCurrentOne() {$this->account->setPrimaryCurrency(Currency::USD());$money = new Money(100, Currency::USD()); //That's 1 USD$this->account->deposit($money);$this->account->setSecondaryCurrency(Currency::EUR());$money = new Money(100, Currency::EUR()); //That's 1 EURO = 1.5 USD$this->account->deposit($money);$this->account->withdraw(new Money(200, Currency::USD())); //That's 2 USD$this->assertEquals(new Money(0, Currency::USD()), $this->account->getPrimaryBalance());$this->assertEquals(new Money(34, Currency::EUR()), $this->account->getSecondaryBalance());}

We set our account’s primary currency to USD and deposit one dollar. Then we set the secondary currency to EUR and deposit one Euro. Then we withdraw two dollars. Finally we expect to remain with zero dollars and 0.34 Euros. Of course this test throws an exception, so we have to implement a solution to our dilemma.

function withdraw(Money $money) {$this->validateCurrencyFor($money);if ($this->primaryCurrency == $money->getCurrency()) {if( $this->primaryBalance >= $money ) {$this->primaryBalance = $this->primaryBalance->subtract($money);}else{$ourMoney = $this->primaryBalance->add($this->secondaryToPrimary());$remainingMoney = $ourMoney->subtract($money);$this->primaryBalance = new Money(0, $this->primaryCurrency);$this->secondaryBalance = (new Exchange())->convert($remainingMoney, $this->secondaryCurrency);}} else {$this->secondaryBalance = $this->secondaryBalance->subtract($money);}}private function secondaryToPrimary() {return (new Exchange())->convert($this->secondaryBalance, $this->primaryCurrency);}

Wow, lot of changes had to be made to support this automatic conversion. What is happening is that if we are in the case of extracting from primary currency and we have not enough money, we convert our balance of the secondary currency to primary and try the subtraction again. If we still do not have enough money, the $ourMoney object will throw the appropriate exception. Otherwise we will set our primary balance to zero and we will convert the remaining money back to the secondary currency and set our secondary balance to that value.

It remains up to our account’s logic to implement or not a similar automatic conversion for secondary currency also. We will not implement such a symmetrical logic. If you like the idea, consider it an exercise for you. Also think about a more generic private method that would do the magic of auto-conversions in both cases.

This complex change to our logic also forces us to update another of our tests. Whenever we want to auto-convert we must have a balance, even if it is just zero.

/** * @expectedException Exception * @expectedExceptionMessage Subtracted money is more than what we have */function testItThrowsExceptionIfWeTryToSubtractMoreMoneyThanWeHave() {$this->account->setPrimaryCurrency(Currency::EUR());$money = new Money(100, Currency::EUR()); //That's 1 EURO$this->account->deposit($money);$this->account->setSecondaryCurrency(Currency::USD());$money = new Money(0, Currency::USD());$this->account->deposit($money);$this->account->withdraw(new Money(150, Currency::EUR()));}

Allocating Money Between Accounts

The last method we need to implement on Money is allocate. This is the logic that decides what to do when dividing money between different account can’t be made exactly. For example, if we have 10 cents and we want to allocate them between two accounts in a proportion of 30-70 percents, that is easy. One account will get three cents and the other seven. However, if we want to make the same 30-70 ratio allocation of five cents, we have a problem. The exact allocation would be 1.5 cents in one account and 3.5 in the other. But we can not divide cents, so we have to implement our own algorithm to allocate the money.

There can be several solutions to this problem, one common algorithm is to add one cent sequentially to each account. If an account has more cents then its exact mathematical value it should be eliminated from the allocation list and receive no further money. Here is a graphical representation.

Allocations

And a test to prove our point is below.

function testItCanAllocateMoneyBetween2Accounts() {$a1 = $this->anAccount();$a2 = $this->anAccount();$money = new Money(5, Currency::USD());$money->allocate($a1, $a2, 30, 70);$this->assertEquals(new Money(2, Currency::USD()), $a1->getPrimaryBalance());$this->assertEquals(new Money(3, Currency::USD()), $a2->getPrimaryBalance());}private function anAccount() {$account = new Account(1);$account->setPrimaryCurrency(Currency::USD());$account->deposit(new Money(0, Currency::USD()));return $account;}

We just create a Money object with five cents and two accounts. We call allocate and expect the 2-3 values to be in the two accounts. We also created a helper method to quickly create accounts. The test fails, as expected, but we can make it pass quite easily.

function allocate(Account $a1, Account $a2, $a1Percent, $a2Percent) {$exactA1Balance = $this->amount * $a1Percent / 100;$exactA2Balance = $this->amount * $a2Percent / 100;$oneCent = new Money(1, $this->currency);while ($this->amount > 0) {if ($a1->getPrimaryBalance()->getAmount() < $exactA1Balance) {$a1->deposit($oneCent);$this->amount--;}if ($this->amount <= 0)break;if ($a2->getPrimaryBalance()->getAmount() < $exactA2Balance) {$a2->deposit($oneCent);$this->amount--;}}}

Well, not the simplest code, but it’s working correctly as the passing of our test proves. The only thing we can still do to this code is to reduce the small duplication inside the while loop.

function allocate(Account $a1, Account $a2, $a1Percent, $a2Percent) {$exactA1Balance = $this->amount * $a1Percent / 100;$exactA2Balance = $this->amount * $a2Percent / 100;while ($this->amount > 0) {$this->allocateTo($a1, $exactA1Balance);if ($this->amount <= 0)break;$this->allocateTo($a2, $exactA2Balance);}}private function allocateTo($account, $exactBalance) {if ($account->getPrimaryBalance()->getAmount() < $exactBalance) {$account->deposit(new Money(1, $this->currency));$this->amount--;}}

Final Thoughts

What I find amazing with this little pattern is the large range of cases where we can apply it.

We are done with our Money Pattern. We saw that it is quite a simple pattern, which encapsulates the specifics of the money concept. We also saw that this encapsulation alleviates the burden of computations from Account. Account can concentrate on representing the concept from a higher level, from the point of view of the bank. Account can implement methods like connection with account holders, IDs, transactions and money. It will be an orchestrator not a calculator. Money will take care of calculations.

What I find amazing with this little pattern is the large range of cases where we can apply it. Basically, every time you have a value-unit pair, you can use it. Imagine you have a weather application and you want to implement a representation for temperature. That would be the equivalent of our Money object. You can use Fahrenheit or Celsius as currencies.

Another use case is when you have a mapping application and you want to represent distances between points. You can easily use this pattern to switch between Metric or Imperial measurements. When you work with simple units, you can drop the Exchange object and implement the simple conversion logic inside your “Money” object.

So, I hope you enjoyed this tutorial and I am eager to hear about the different ways that you might use this concept. Thank you for reading.


Original Link: http://feedproxy.google.com/~r/nettuts/~3/ouFOBGHDIkc/

Share this article:    Share on Facebook
View Full Article

TutsPlus - Code

Tuts+ is a site aimed at web developers and designers offering tutorials and articles on technologies, skills and techniques to improve how you design and build websites.

More About this Source Visit TutsPlus - Code