/ object-design

# Trust in objects. They have the right to decide.

(Para leer este post en español, siga este link)

Real devs don't use if. This slogan was my first contact with 10Pines, way before starting my apprenticeship residence. The first time I heard that sentence, it really got stuck in my head. Why would somebody want to eliminate the `if` clause? What was wrong about it?

My goal with this post is to prove why a real developer shouldn't use `if`. To get there, I'll explain some of the benefits obtained by the usage of an alternative: Let the object make the decisions.

Just as it sounds. The objects are completely capable of handling the task, they just need to be allowed to. The key for this, is to take advantage of their polymorphic nature to provide better collaborations.

## Just an example

Let's see an exercise solved in Ruby where we can replace all the `if` clauses using collaborations. We'll go step by step improving our model and seeing the benefits of this approach.

This is our assignment:

Make a model of the numbers in OOP. Consider the existence of Integer
numbers and fraction numbers. The numbers must respond properly to the messages `#add` and `#subtract`.

Our task seems to be easy, we all know how to add and subtract. However we'll focus on the model instead of on the arithmetic part.

## First approach

Let's do a short analysis of the problem. Our goal is to make a model that contemplates different types of numbers (Integers and Fractions) allowing operations between them. Our model should accept operations like this:

• 1 + 1 = 2
• ½ - ⅓ = ⅙
• 1 - ½ = ½
• ⅔ + ⅓ = 1

It is important to note that the receiver of the message could be any type of
number. So does the external collaborator. In consequence, we couldn't always use the same algorithm, we have to adapt it to the particular situation. For example: Adding two integers is a straightforward operation. Nevertheless, adding an integer to a fraction is not that simple. We have to do something extra to solve such operation (i.e. multiplying the integer by the denominator of the fraction). If we've got two fractions as operands are we should look for a common denominator. In conclusion, there are different algorithms for every situation, and we'll need one per case.

Having all this in mind we can start building our model. It sounds pretty natural to have a class diagram like this: The class Number is abstract. Fraction and Integer must know how to add and
subtract any number. However, the mechanism of the operation can't be the same for any type of number as we've mentioned before. A First approach could be the following:

``````class Object
def subclass_responsibility
fail 'Should be implemented in subclass'
end
end

class Number
subclass_responsibility
end

def - (aSubtrahend)
subclass_responsibility
end
end

class Integer < Number
# returns the sum considering the integer
# returns the sum considering the fraction
else
raise "Unexpected type of number"
end
end

def - (aSubtrahend)
# returns the difference considering the integer
# returns the difference considering the fraction
else
raise "Unexpected type of number"
end
end
end

class Fraction < Number
#Implentation is similar to Integer case.
end

def - (aSubtrahend)
#Implentation is similar to Integer case.
end
end
``````

The Number class is abstract. All its methods delegate the implementation responsability to subclasses. All the concrete
his class and then do the calc.

It works, but let's compare it with a solution without using the 'if' clause.

## Better Legibility

The purpose of the if in this case is to decide what to do based on the class
of the collaborator. However, this isn't the only way to do that. The object itself doesn't know the external collaborators' classes, but it does know
its own class, therefore it can send the right message to the collaborator with enough context information to do the right thing.

With this in mind, 1 - ⅓ = ⅔ could be solved like this:

1. We ask 1 to subtract certain number.
2. The object 1 doesn't know what kind of number is the parameter, so it tells itself: "Hey, I'm an integer, please subtract yourself from me, having that in mind".
3. The ⅓ know how to handle the specific situation of substrating from an integer, so the operation is performed and the expected result is returned.

Let's see how it works:

``````class Integer < Number
end

def - (aSubtrahend)
aSubtrahend.subtract_from_an_integer(self)
end

#returns the sum of two integers
end

#returns the sum of an integer plus a fraction
end

def subtract_from_an_integer(aMinuend)
#returns the difference of two integers
end

def subtract_from_a_fraction(aMinuend)
#returns the difference of a fraction minus an integer
end

end

class Fraction < Number
end

def - (aSubtrahend)
aSubtrahend.subtract_from_a_fraction(self)
end

#returns the sum of an integer plus a fraction
end

#returns the plus of two fractions
end

def subtract_from_an_integer(aMinuend)
#returns the result of an integer minus a fraction
end

def subtract_from_a_fraction(aMinuend)
#returns subtract of two fractions
end
end
``````

Our Number class also has to be adapted to this new approach. Defining the
methods in the abstract class is very important, because it helps to understand the model sooner and expand the behavior easier.

``````class Number

subclass_responsibility
end

def - (aSubtrahend)
subclass_responsibility
end

subclass_responsibility
end

def subtract_from_an_integer(aMinuend)
subclass_responsibility
end

#{...}

end
``````

Maybe at first sight it seems difficult to read, but let's follow a simple collaboration step by step to see that we get exactly what we are looking for.

``````Integer.new(3) + Fraction.new(1,5).
``````
1. The message `#+` is sent to the object 3 with the external collaborator ⅕.
2. The 3 sends the message `#add_from_an_integer` to the ⅕ with himself as external collaborator.
3. The ⅕ knows specifically what to do because they tell him to add an Integer.
4. The correct result is returned.

Using the "go to definition" feature of our favorite IDE we can follow this code without problem. Even debugging gets more pleasant because it's linear: You just need to follow the value of the variables, there are no weird jumps in the code, you don't have to recreate conditions in your head to find the next line of code to be executed. Note that the objects are handling the decisions for us.

This point is key. The objects are capable of handling this type of situations (and several much more complex!) by themselves, they don't need the constant presence of a programmer manifested in the form of an 'if' clause. Taking advantage of polymorphism we can create objects that handle the situations themselves.

Of course the essential difficulty of the problem is still there. We need to do the things right to get the right result. For example, in the message `#-` we have to consider that the minus operation is not commutative. For this reason the messages `#subtract_from_an_integer` and `#substract_from_a_fraction` must consider the external collaborator as a minuend and not as a subtrahend. To avoid problems the name of the message decribes it explicity.

## Easy Expansion

Suppose we want to extend our model by adding the class `IrrationalNumber`. With the `if` approach this would get really tedious. We would have to inspect every method on every class and add an `elsif` statement with the new behavior (besides writing the new class). Moreover, nothing ensures that we haven't miss an `if` statement somewhere else.

Now we'll see how in the polymorphic version we can add all the necessary code to make our new functionality work in an easy way without touching anything of the existing code plus being guided by the model itself.

Just to start we can create the class `IrrationalNumber` that inherits from
`Number`. At the beginning this class can define only the messages `#+` and `#-`.

``````class Irrational < Number
end

def -(aSubtrahend)
aSubtrahend.subtract_from_an_irrational(self)
end

#{...}
end
``````

Now it's clear that any number should respond to the messages `#add_from_an_irrational` and `#subtract_from_an_irrational`, so we need to define this methods in the `Number` class.

``````class Number

#{...}

sublclass_responsibility
end

def subtract_from_an_irrational (aSubtrahend)
subclass_responsibility
end

end
``````

At this point if we ran any test with our new class the result would be the same: "Should be implemented in the subclass". And here it is: The model is not just telling us what message should be answered, it also tells us who has to answer. "An integer should implement the message `#add_from_an_irrational`". The model itself guides us over the expansion. It would take only writing a couple of tests to know exactly how to expand our model.

``````class Irrational < Number
end

def -(aSubtrahend)
aSubtrahend.subtract_from_an_irrational(self)
end

# integer plus irrational
end

# {...}

# irrational plus irrational
end

def subtract_from_an_irrational(aMinuend)
# irrational minus irrational
end
end
``````

In consequence now all our classes need to know how to respond `#add_from_an_irrational` and `#subtract_from_an_irrational`.

The expansion was easy, we didn't need to touch any of the previous code and the model guided us over the expansion. What else can we ask?

## Conclusions

It's true that, at first, all this stuff of delegating the work from an object to another "just to extract an if" can sound a little bit daunting. However, look at all we have accomplished. The final model is robust, simple to understand and pretty easy to extend. We did a lot more than just "extracting an if". In our polymorphic model the objects are responsible of handling the problems and guiding us over the extension of it.

The `if` is not "bad" in itself. In some situations it is just inevitable and
'right for the job'. Using the same example but with the `#/` message it's very difficult to avoid the `if` that verifies that the divisor is not zero. To avoid that `if` we would have to make a special class for zero and a specific dispatch, and that would make the solution too complicated. The `if` in this case would be completely justified.

I like to see the if as a "smell". Every time it appears I think wether I'm losing the chance of using polymorphism and improve my model.

Finally, I'd like to mention something important. In this post I put all my effort to show the benefits of avoiding the `if`. However, the solution I use came out from nowhere, I had never explained the mechanism I used to came out with the solution. So, there is basically two open questions:

• Is there a deductive method or an algorithm exist to remove the `if`?
• Can we always replace the `if` with polymorphism? Is there a limit?

If you are interested in the answer I strongly recomend to see this
webinar
, where Hernán Wilkinson explain this topic in depth. 