Skip to content →

Value Objects

Value Objects

This is the first of many posts on Domain Driven Design and the many concepts described in the seminal book by Eric Evans.

The goal of this concept is to keep like functionality on immutable objects together. Let’s dive right into an example.

The Problem

Say you have the following code:

class VendingMachine
  def validate_coin(image)
    # code
  end
  
  def choose_item(cents, item)
    if can_choose_item?(cents, item)
      vend_item(item)
    else
      message(:insert_more_money)
    end
  end
  
  def can_choose_item?(coins, item)
    # code
  end
  
  def insert_coin(image)
    if validate_coin_image(image)
      @cents += extract_coin_value
      ingest_held_coin
    else
      release_coin
    end
  end
  
  def extract_coin_value(image)
    # returns cents
  end
  
  def add_to_total(cents)
    # code
  end
  
  # ... snip ...
end

This is a nice bit of code, but the concept of a coin is intermingled with a vending machine. Oops.

The Solution

Let’s extract the concept of a coin into a Value Object.

class Coin
  attr_accessor :cents, :denomination
  def self.from_image(image)
    denomination = extract_coin_denomination_from_image(image)
    new(denomination)
  end
  
  def initialize(denomination)
    @denomination = denomination
    @cents = denomination.cents if denomination
  end
  
  def valid?
    @cents &&
    @denomination
  end
end

Here’s the resulting vending machine class.

class VendingMachine
  def choose_item(item)
    if can_choose_item?(item)
      vend_item(item)
    else
      message(:insert_more_money)
    end
  end
  
  def can_choose_item?(item)
    item.cost == @cents
  end
  
  def insert_coin(image)
    coin = Coin.from_image(image)
    if coin.valid?
      add_to_total(coin)
      ingest_held_coin
    else
      release_held_coin
    end
  end
  
  def add_to_total(coin)
    @cents += coin.cents
  end
  
  # ... snip ...
end

You can see here that I’m realizing I need the concept of an Item, which has the concept of cost encoded as cents!

Just spitballing here, but if you were a vending machine operator, and you wanted to know if someone accidentally adds a vintage coin worth more than its denomination, you could add a year property to Coin and then sort out all the valuable coins to go into a separate compartment.

Clearly, this type of design thinking is infectious.

Now, you could imagine if we wanted to add giving change back to the user, it would be a bit easier to enumerate the coins. You could also add a class method to Coin that would allow you to pass in a cents value and a list of available denominations to make change.

Good Value Objects effortlessly collect related functions together, and make your life easier, not harder.

As mentioned above, Value Objects infect the code, in a good way: the next developer that comes along will see how you’re using the value object, and re-use it elsewhere. And they might think of new Value Objects to extract from the system. It’s a meme with positive benefits.

Properties

Immutability

Coin.new(:quarter).denomination = :dime # => MethodNotFound: Coin#denomination=

Comparability

Coin.new(:quarter) == Coin.new(:quarter)

Good for

  • encapsulation of logic
  • single responsibility
  • organization

Real-world examples

  • Money
  • Geolocation
  • Url
  • Date/Time
  • and the list goes on…

Conclusion

This pattern is part of what some call “Infectious Design.” [link to unwritten article here, lol]

I have no reservations about using these in any codebase. They’re a staple for me, and have always served me well. And it’s hard to go overly wild with Value Objects, as they usually have a very contained definition.

Happy Value-Object-ing!

Published in Domain Driven Design