As the agency which took Ruby as the language of choice, Syndicode always tracks the best practices and useful materials on this topic. Last time we told you about The Template Method in Ruby. And today we’re going to share with you an information on how to structure your code in Ruby.
There are three approaches to structure your code in Ruby we’ll review today.
The first one and the easiest one is inheritance. But in object-oriented languages like Ruby inheritance could lead to the structural complexity. An inheritance tree will grow every time the client asks for the new features or an improvement.
When you face this problem, instead of reducing code duplication, you can end up with having the same logic in many places. There is even a Wikipedia article describing this phenomenon.
But we can avoid code duplication with mixins and composition.
Mixins are usually the first thing comes to the minds of Ruby programmers when they notice that the inheritance is not a solution. Basically, mixins are modules with a set of methods that can be included into a class and become an indistinguishable part of it. We can use them to extract any common logic and avoid code duplication.
We need to create those modules that we’ll include later on. Then we can define specific classes and include the mixins, that we’ve created. This way no code is duplicated and we can add any level of specialization and easily build whatever we want with all the features we need. Mixins might work well when you want to define meta-behavior of a class like logging, authorization or validation. The good thing is that they keep the code clean and small.
There are still some problems though. When you look at this class you’re not sure how the included behavior is used. A mixin adds a couple of new methods but it’s not immediately obvious what they are, how does the class interfere with them and how does it affect the execution flow. If by any chance two modules contain methods with the same name, you’re gonna run into problems – one module will silently use the method from the other one. In the same way, a module can mess up the code in your own class.
They’re fine as long as you trust their implementation and know that they don’t break any other logic.
How does the composition work? Instead of trying to share the same behavior between classes, you should identify what kind of concepts are these things that differ, name them, extract into separate classes and then compose into your final object.
If inheritance is about is-a relationship, then the composition is about has-a. Therefore we’ve got to change the structure of our problem in order to leverage the composition. We need to identify our concepts.
After defining the main class we need we need to create the implementation of our concepts and put everything together.
There are no problems with conflicting names. Each class does exactly one thing (satisfying the single responsibility principle). Therefore you can easily test each of them by checking how well do they do this only thing. We also achieve high cohesion (keeping the same logic together) maintaining low coupling (making classes loosely dependent on each other) at the same time. With the composition, we can easily change the code without changing the interface.
It tends to make the code longer, especially when it comes to injecting all the dependencies into the final object. You have to write additional boilerplate in order to store references, setup delegations and enforce correct execution flow. As a remedy, you can use one of many creational patterns, like Factory or Builder.
So, what we have?
Inheritance is the first choice for many programmers but it makes code complicated and hard to maintain.
Mixins seem like a smart and more powerful replacement but they are just a way to achieve implicit multi-base inheritance.
The composition is the most straightforward and clear approach to maintain dependencies between classes.
As always, you have to choose depending on your project and experience.
Based on How to avoid inheritance in Ruby? article by Michal Konarski. In his original article, you can find code examples.
If you want to find more interesting information, subscribe to our weekly newsletter!