What is a Value Object?
As stated by Martin Fowler a value object is:
A small simple object, like money or a date range, whose equality isn’t based on identity.
two points:
- A value object represents a simple entity whose equality is based on its value,
- Value objects should be immutable
Why do we need them?
- the separation of concerns
- it allows you to combine behaviour with the data and add functionality to data without polluting the model
- by isolating functionality your code is easier to test
- removes duplication
- improves the understanding and organisation of code. Operations on particular data are now gathered in a single place, instead of disperse throughout the code
use cases
- Arguments together all the time
- One attribute with behaviour
- Two inseparable attributes value and unit
- Class enumerable
Arguments together all the time
The first situation happens when we have two or more arguments that are passed and used together all the time, often known as a “Data Clump” (code smell). A date range is a common example, when start_date and end_date are passed together all the time in our methods. We can create a class DateRange with the attributes start_date and end_date and this class should be responsible for the start_date and end_date columns of a given ActiveRecord object and accommodate all the related behaviour. We could include methods like include_date?(date), include_date_range?(date_range), overlap_date_range?(date_range) and to_s. This class can look something like this:
1 |
class DateRange |
This is just a standard Ruby object that does not inherit from ActiveRecord::Base. This class can be used, for example, with an Event model with the following columns: name, description, address_city, address_state, starts_at, ends_at. The Event model could look something like this:
1 |
class Event < ActiveRecord::Base |
With all this in place, we get the following usage:
1 |
> event = Event.create(name: 'Ruby conf', start_date: Date.today, end_date: Date.today + 1.days) |
As I mentioned previously, one of the advantages of extracting code that usually goes in the model and create value objects is that you can reuse them and here is an example of that since we could use the DateRange object in the other model as well.
One attribute with behaviour
Another situation where a value object can be useful is when you have one simple attribute that needs some associated behaviour and such behaviour does not belong in the model. Imagine that you have a model Room that inherits from ActiveRecord::Base with a degrees attribute and then you add a Temperature class to answer some questions that your system may need related with the temperature value:
1 |
class Temperature |
We get the following usage:
1 |
> room_1 = Room.create(degrees: 10) |
Two inseparable attributes value and unit
A very popular one is the money gem, which helps you deal with money and currency conventions by providing a Money class that encapsulates all information about a certain amount of money such as its currency and value. The gem readme file is very thorough and self-explanatory so if you are interested go ahead and take a look. You can use it in a model class like Product:
1 |
class Product < ActiveRecord::Base |
In this case when asking for or setting the cost of a product, we would use a Money instance.
1 |
|
Class enumerable
It is common practice to define a value object in Rails models by creating an array class like this:
1 |
class Event < ActiveRecord::Base |
This practice is not good because the array values may be used in the model attributes but they have nothing to do directly with the model domain. Defining the value object like this has a few disadvantages like impossibility to add functionality to the value object without polluting the model and does not allow to reuse the object. So we can create an object to accommodate the data of that array and also add some useful methods if we need them. The value object class for the example above could look something like this:
1 |
class Size |
We can get the set of possible sizes and have a method that can be used in select form fields. I think this is useful if you have more than one model that has the attribute size, if your model has a lot of those arrays and you want to slim your model or if you have logic associated with it. Another common example of this type is Color, when we need to have a set of colors that can be used in some persisted models.
近期评论