“Prefer composition over inheritance” is a popular saying amongst programmers. In this article, I want to demonstrate what that looks like.

We’re going to refactor some Rails classes from inheritance to composition. Then we’ll refactor again from composition to something even better: data.

Inheritance (The Original Code)

In an attempt to show “real” code, I’ve taken this example from the Rails codebase. These classes originally came from action_view/helpers/tags . However, I have simplified the code by removing some of the unrelated parts.

This implementation starts with DatetimeField :

class DatetimeField def render ( date ) value = format_date ( date ) type = self . class . field_type "<input type=' #{ type } ' value=' #{ value } ' />" end def format_date ( date ) raise NotImplementedError end def self . field_type 'datetime' end end

This class is used to render HTML input tags that contain dates/times, for example:

DatetimeField . new . render ( Time . now ) #=> "<input type='datetime' value='2017-06-26T20:28:20' />"

The above code will not work, however, because DatetimeField is an abstract base class. Notice the NotImplementedError raised in format_date . This class is designed to be inherited from, and the subclasses must override format_date .

There are four subclasses that inherit from DatetimeField :

class DatetimeLocalField < DatetimeField def self . field_type "datetime-local" end def format_date ( value ) value . try ( :strftime , "%Y-%m-%dT%T" ) end end class DateField < DatetimeField def format_date ( value ) value . try ( :strftime , "%Y-%m-%d" ) end end class MonthField < DatetimeField def format_date ( value ) value . try ( :strftime , "%Y-%m" ) end end class WeekField < DatetimeField def format_date ( value ) value . try ( :strftime , "%Y-W%V" ) end end

So the output of render depends on which subclass is being used. This is essentially the template method design pattern.

Refactoring To Composition

This refactoring involves separating the base class from all the subclasses. Each subclass will be turned into a collaborator – a separate object that the base class uses. Where there used to be a single object, there will now be two different objects.

The first question to ask is: how does the base class use the subclasses?

The base class calls format_date to turn a date into a string. The base class allows subclasses to override the default field_type using a class method.

This will be the interface between the two objects.

Secondly, we need a name for these collaborator objects. It appears to me that the subclasses are date formatters, not date fields, so let’s call these collaborators “formatters”.

If we were using a statically-typed language, this is the point where we would define the interface. For example, this is what the interface would look like in Swift:

protocol DatetimeFieldFormatter { func format ( date : DateTime ) -> String var fieldType : String { get } }

But this is Ruby, and we don’t have interfaces, we have duck types instead. A duck type is a kind of implicit interface. We still have to conform to the interface by implementing all the expected methods, but the expected methods haven’t been declared with code.

Knowing the interface, we can refactor the base class to use a collaborator called formatter :

class DatetimeField attr_reader :formatter def initialize ( formatter ) @formatter = formatter end def render ( date ) value = formatter . format_date ( date ) type = formatter . field_type || 'datetime' "<input type=' #{ type } ' value=' #{ value } ' />" end end

The collaborator is provided as an argument to initialize , and stored in an instance variable. Later on, it is used to format dates, and possibly override the tag type. This is an example of dependency injection.

Now lets refactor all the subclasses into classes that implement the formatter interface:

class DatetimeLocalFormatter def format_date ( value ) value . try ( :strftime , "%Y-%m-%dT%T" ) end def field_type "datetime-local" end end class DateFormatter def format_date ( value ) value . try ( :strftime , "%Y-%m-%d" ) end def field_type nil end end class MonthFormatter def format_date ( value ) value . try ( :strftime , "%Y-%m" ) end def field_type nil end end class WeekFormatter def format_date ( value ) value . try ( :strftime , "%Y-W%V" ) end def field_type nil end end

Notice how all the classes have been renamed to Formatter , and they don’t inherit from anything.

Now rendering the output is done like this:

DatetimeField . new ( WeekFormatter . new ). render ( Time . now ) #=> "<input type='datetime' value='2017-06' />"

Bonus Refactor: Singletons

Let’s take a short detour into functional programming town.

The formatter classes don’t have any instance variables. To put it another way, they contain only pure functions and no state. All instances of the same class will be identical, so we only ever need to have one. This is why they don’t need to be classes.

In this situation, I prefer to use singleton modules:

module WeekFormatter extend self def format_date ( value ) value . try ( :strftime , "%Y-W%V" ) end def field_type nil end end

Now WeekFormatter is an object that contains all the methods, and you don’t ever need to use new . It is essentially a namespace of functions.

End of detour.

Refactoring To Data

Savvy readers may have noticed that the refactored formatter objects don’t contain much code. There is duplication too, because every formatter contains value.try(:strftime, ...) . In fact, only two strings are differ between each formatter class: the date format string and the field_type string.

When a class contains data, but doesn’t contain any behaviour, it doesn’t need to be a class. It could just be a Hash instead.

If we turn all the formatter classes into plain old Hash s, and pull all the behaviour up into the DatetimeField class, it would look like this:

class DatetimeField FORMATS = { local: { strftime_format: "%Y-%m-%dT%T" , field_type: "datetime-local" }, date: { strftime_format: "%Y-%m-%d" }, month: { strftime_format: "%Y-%m" }, week: { strftime_format: "%Y-W%V" }, } attr_reader :format def initialize ( format_key ) @format = FORMATS . fetch ( format_key ) end def render ( date ) value = date . try ( :strftime , format . fetch ( :strftime_format )) type = format . fetch ( :field_type , 'datetime' ) "<input type=' #{ type } ' value=' #{ value } ' />" end end

Rendering the output is done like this:

DatetimeField . new ( :week ). render ( Time . now ) #=> "<input type='datetime' value='2017-06' />"

This is the best implementation, in my opinion. Five classes were reduced to a single class. It requires less code. Everything is closer together, instead of being spread across multiple files.

Not to be confused with domain-driven design (DDD).

This is an example of what I call “data-driven design” or just “thinking in data”. All the specific details are described in data: numbers, strings, arrays, hashes, structs, etc. All the behaviour (i.e. the implementation) is generic, and controlled by the data.

I really like this approach. You can use it any situation where the details can be represented as pure data, with no behaviour. But when different cases require significantly different behaviour, collaborator objects are the better choice.