7 minutes reading time (1436 words)

The Value of Value Objects

The Value of Value Objects

Do you know why is it important for value objects to be immutable? Maybe you're not familiar with a concept of value objects, but don't worry I'll explain everything in the simplest way. Let's gamble a bit!

What is a Value Object?

First of all we need a definition of a value object. From Wikipedia we've got that a value object is a small object that represents a simple entity whose equality is not based on identity: i.e. two value objects are equal when they have the same value, not necessarily being the same object.

Yeah, I know it can be confusing so let's simplify it a bit. Understanding what a value object is will be easier when we compare it to entities. When we're building a new application we divide it into classes. In these classes we can find services, factories, builders and so on, but also entities and value objects. There is a high probability that you're using value objects but you just don't know that these objects are called that way.

Let's start from entities. These are those objects which have identity – for example for e-commerce application a Product object is an entity, an Order object is an entity and a Customer object is also an entity. Identity is what all of these classes have in common. That's important, because we can have two similar objects of a User with the same first name, last name and even date of birth but with different identity so these objects are not equal. We compare entities by their identities. We can also say that we compare entities by theirs reference - they have to reference the same object to be considered equal.

Now we can move to value objects. On the first look value objects look almost the same as entities. The major difference is that value objects haven't got an identity. For example a Date object - two objects with a date of 2014-06-12 are equal and that's it. We don't care about their identity. We compare value objects by their... value. They don't have to be the same object. It's enough to consider them equal when they have the same value.

These classes can be considered as a value object: Date, Money, Email, Address, Range. Of course, we can't say that these objects are always value objects – it always depends on a context. The best explanation for this is a Money class. For e-commerce system a money class is a value object, eg. two one hundred-dollar bills are the same for anyone.

We can easily switch these bills and no one will loose or gain any value – they're equal. But for the government it's not that obvious. For them my one hundred-dollar bill is not the same as yours because they care about the identity of each bill. In that context a Money object wouldn't be a value object but it would be an entity.

Problem: Value Objects Should Not Be Mutable

On the internet are many examples of value objects. For me the best one which perfectly described why these little objects should be immutable are: money and date. I'm going to explain a mutability problem using money.

We're building a lottery application. For now we have two gamblers: Kate and Paul. It's not important how the lottery system works because we wanna focus only for the best part of it – payments. We need a method that will reward players (for simplicity, our lottery is going to use only dollars as a currency). Also we want make our lottery more attractive so we're going to give every player a free $10!

class Lottery
{
	/** @var Gambler[] */
	private $gamblers;
/** @var Dollar */ private $initialBalance;
public function __construct() { // @todo: get value of a intial balance from a database $this->initialBalance = new Dollar(10); $this->gamblers = array(); }
public function enroll(Person $person) { $this->gamblers[$person->getName()] = new Gambler($person, $this->initialBalance); }
private function rewardWinners(array $gamblers, Dollar $amount) { foreach($gamblers as $gambler) { $gambler->addReward($amount); } } }

and a Gambler class:

class Gambler
{
    /** @var Person */
    private $person;

    /** @var Dollar */
    private $balance;

    public function __construct(Person $person, $initialBalance)
    {
        $this->person = $person;
        $this->balance = $initialBalance;
    }

    public function addReward(Dollar $amount)
    {
        $this->balance = $this->balance->add($amount);
    }
}

And now we can move into the most interesting part – a Dollar class:

class Dollar
{
    /** @var int */
    private $amount;

    /** @var string */
    private $currency = "USD";

    public function __construct($amount)
    {
        $this->amount = $amount;
    }

    public function setAmount($amount)
    {
        $this->amount = $amount;
    }

    public function getAmount()
    {
        return $this->amount;
    }

    public function add(Dollar $amount)
    {
        $this->amount += $amount->getAmount();
    }
}

It looks that everything is ok, right? So what are we waiting for? Let's gamble!

Initial balance:

Kate: 10USD
Paul: 10USD

Seems good!

1st round: both players won $100

Lottery::rewardWinners(/* Kate & Paul */, new Dollar(100));
Kate: 210USD
Paul: 210USD

Hmm that's strange... but let's take a look at 2nd round

2nd round: only Kate won $200

Lottery::rewardWinners(/* Kate */, new Dollar(200));
Kate: 410USD
Paul: 410USD

That's definitely not what we were expected! The reason is mutability of Dollar class. In whole application we're using the same object of Dollar class! Let's take it into consideration and make these calculations again:

  1. We create a Dollar object with value of $10
  2. We add two times $100 (one for Kate's and one for Peter's reward): $10 + $100 + $100 = $210
  3. We add $200 (Kate's reward): $210 + $200 = $410!

Now everything is clear. We've added rewards always to the same object. Of course we can solve this problem by cloning the Dollar object, but it's almost impossible to always remember that. The better solution is to make a Dollar class immutable. To achieve that we need to do two things:

  1. Get rid of setters
  2. In add() method return a new Dollar object instead of changing the value of the current one

After these changes, our class should look like:

class Dollar
{
    /** @var int */
    private $amount;

    /** @var string */
    private $currency = "USD";

    public function __construct($amount)
    {
        $this->amount = $amount;
    }

    public function add(Dollar $amount)
    {
        return new Dollar($this->amount + $amount->amount);
    }
}

To be honest we don't really need a getAmount() method, so I've deleted it too. Now add() method returns a new object instead of changing a value. Because of that we need to modify addReward() method in Gambler class:

public function addReward(Dollar $amount)
{
    $this->balance = $this->balance->add($amount);
}

And let's gamble again!

START
Kate: 10USD
Paul: 10USD

AFTER FIRST ROUND
Kate: 110USD
Paul: 110USD

AFTER SECOND ROUND
Kate: 310USD
Paul: 110USD

So now everything is correct. You can check it yourself – just clone this project from https://github.com/tomaszhanc/lottery – that's a mutable version, so you can practise and make it to be immutable.

Why have I made a such a big noise about it? Because PHP DateTime class (below 5.5) is mutable. For example that code:

$start = new DateTime('2014-06-12');
$end = $start->add(new DateInterval('P3D'));

print $start->format('Y-m-d'); // prints 2014-06-15
print $end->format('Y-m-d');  // prints 2014-06-15

As you can see that's the same problem which we had with Dollar class before.

I hope I was able to convince you to use immutable value objects instead of mutable ones. If anything from here is not clear, please let me know – I'll do anything to simplify it.

GSoC Project

We (me, Herman and Søren) have talked a lot about theoretical concepts. That's really amazing because I never had anyone to talk about these concepts. It is very informative for me. Unfortunately I'm a bit behind on schedule for this project, but we have laid a good foundation to build the rest on.

To make it short, things what I've already done:

  • the new date class wouldn't be extending PHP DateTime (but there is a getter for a PHP DateTime object)
  • all functionality of the current Joomla's DateTime are provided
  • simple calculations, e.g. you don't need to use DateInterval for adding some days to your date
  • range of dates with useful methods like: includes(), gap(), abuts() and more

What I'm thinking of:

  • moving all getters to a some kind of DateWrapper class
  • providing 'fancy' calculations – I mean add methods for calculations like: $date->add('3 days')

What I'm going to do:

  • timeSince() method
  • provide extensibility (it's going to be done by using strategy pattern)
  • DateFactory

If you have any suggestions how a new date class should look like or what you need from it, please let me know about it!

0
Utiliser SobiPro en tant que CCK
 

Comments

Already Registered? Login Here
No comments made yet. Be the first to submit a comment

By accepting you will be accessing a service provided by a third-party external to https://magazine.joomla.org/