Refactoring in Ruby: The right place for a Builder?

Posted by Ilija Eftimov on January 13, 2016

Recently I started tackling refactoring in Ruby with my blog posts. It seems
that it’s one of the most popular topics that people would like to read about,
so here I am with another installment of refactoring in Ruby. This time, we will
see if it’s the right time and place for a Builder… whatever that means.

Just a bit of backstory

In my last post on refactoring in Ruby, I covered how to detect and refactor the
long parameters code smell. It stirred
up quite a discussion in the comments, but also, on my Skype chat. So, a
colleague of mine, who I respect a lot, mentioned that eliminating the long
parameters lists can be done via the builder pattern. I won’t go into too much
detail of how the discussion went, but I’ll tell you that we had a bit of a
disagreement.

Eventually we shared some thoughts and got on the same page. So, in this blog
post I would like to show you what is the Builder Pattern and what’s a good
place to use it.

The basics

First, the basics - what is the Builder Pattern? The builder pattern is an
object creation design pattern. This means that the code produced when
following this pattern will create objects of certain class(es).

Now, the key thing to the Builder Pattern, which differentiates it from the
Factory pattern, is it’s purpose. The Builder pattern is used to apply any sort
of configuration on object creation.

This pattern also allows finer control over the object creation. Via the
builder’s interface we create an uniformed way of object creation, instead of
dealing with complex flows of creating an object (which often can be a
composite).

Long parameters lists

Now, back to that topic on the long parameters lists code smell. Does it make
sense to apply the Builder pattern to this type of a code smell? Well, it seems
like it does, but only to a special type of methods - constructors!

If you know just a bit of OOP, you know that constructors are the methods which
are invoked when an object is being created. Now, if a constructor has a long
list of parameters, that usually means that it is doing quite a bit of
configuration of the object itself.

This is quite simlpe, but it implies that you will need to create objects from
the CPU and Touchscreen classes, before you create a Smartphone object.
It means that you need some configuration done before you can create an actual
Smartphone.

As you can see, a big parameters lists can lead to confusion. If in the source
code of the application you will only create Smartphone objects couple of
times, then refactoring this is an overkill. But, if the class with the long
parameters list in the constructor is used often, then streamlining the object
creation will be an immediate win.

Before applying the Builder Pattern here, we need to detect some patterns in our
code. By code, I mean production code and tests. If you often find yourself
forgetting to create a CPU object before assigning it to a Smartphone, then
a builder can help with all of that. Basically, a builder should have utility
methods that will make the configuration of these objects easier.

takes effort, because it’s nearly impossible to remember the order of the
parameters. Meta-note: When I was writing the example, I had to copy-paste the initializer
arguments and fill them up one-by-one, so I don’t mix them up.

As you can see, although the example is quite contrived, introducing the builder
pattern comes in handy. Sure, the second code example might be more verbose, but
on the other hand it’s super clear and very hard to get confused by it. There
are a ton of other situations where one could use the builder pattern, but in
this case it plays nicely when eliminating this code smell.

Edited, 16th of January, 2016:

Additionally, thanks to couple of readers and colleagues, who I thank a ton
for weighing in, a very interesting and approach to builders is using a building
block. The usage would look like:

As you can see, this looks super nice, which is half the reason I agreed to add
this to the post after it was published. The other half is usefulness, obviously.
The implementation of the block-style builder:

By implementing the SmartphoneBuilder.build method, we expose the additional
block-style variant of the builder, so the user can choose between the
good-looking block building or the not-so-good-looking non-block-style version.

(Over) Validation

Earlier we agreed that as an object creational patten, the Builder comes into
play when there’s some configuration heavy lifting when creating objects. Well,
another place where a Builder might fit in nicely is validation-on-creation.

There have been plenty of cases where we want to make sure that the data
that is passed in on object creation is valid. Like, a smartphone without a
touchscreen is not really a smartphone, is it? Well, at least for now.

Anyway, the Builder Pattern comes nicely into play when we want to deal with
validation. Since the data is pushed into the object in it’s constructor, the
most logical place to do the validation is there, in the constructor:

This adds additional weight on the Smartphone class. One option to reduce the
complexity in the class would be to move this logic to a validator class. But,
since all of this logic is related to the configuration of a Smartphone object
on creation, moving the logic to the SmarphoneBuilder will ensure that the
objects are always safely configurated.

Let’s refactor the Smartphone class and move the validation logic to the
SmartphoneBuilder:

As you can see, whenever we set any property on the builder, it validates the
configuration and raises an error if the configuration is broken. In this
way, the Builder Pattern gives us the security that we will always get valid and
sane objects out of it. Let’s say, we want to make sure that the phone memory
must be between 1GB and 64GBs, by adding additional validation:

classSmartphoneBuilderVALID_OS=[:ios,:android,:windows,:blackberry,:firefox,:tizen,:ubuntu]attr_reader:smartphonedefinitialize@smartphone=Smartphone.newenddefadd_memory(gbs)validate_presence!("Memory",gbs)validate_memory!(gbs)@smartphone.memory=gbsenddefadd_ram(gbs)validate_presence!("RAM",gbs)@smartphone.ram=gbsenddefadd_screen(screen_size)validate_presence!("Screen size",screen_size)@smartphone.screen=Touchscreen.new(screen_size)enddefset_model(model)validate_presence!("Model",model)@smartphone.model=modelenddefadd_processor(cpu_freq)validate_presence!("Processor Speed",cpu_freq)@smartphone.cpu=CPU.new(cpu_freq)enddefset_os(os)validate_presence!("Operating System",os)validate_os!(os)@smartphone.os=osenddefsmartphone@smartphoneendprivatedefvalidate_memory!(gigabytes)unlessgigabytes.between?(1,64)raise"The memory must be between 1GB and 64GBs"endenddefvalidate_presence!(attr_name,attr_value)raise"#{attr_name} is required."ifattr_value.nil?enddefvalidate_os!(os)unlessVALID_OS.include?(os)raise"Invalid Operating System: #{os}"endendend

As you can see, adding more validation rules in the builder allows us to always
get valid objects, forever and ever.

Outro

In this post we’ve covered, in my opinion, two of most popular cases where a
builder pattern can come in handy.

As a general rule, when you think of applying this pattern, look for places in
your code where confusion appears. Also, a good smell is a lot of configuration
overhead. In these places, you might find this pattern useful.

I am sure you might find other situations where you can apply this pattern. I
would love to learn of new places where this pattern can be used, so please
share your experience with me in the comments section below.