OO Programmer tend to use inheritance as a way of code reuse, a best practice and for better maintainability. I am no exception. But there are times where inheritance can become your worst enemy, and instead of making your life easier, complicate stuff to the point you are lost.
That was a bit dramatic… but I will try to demonstrate my point, but first, a small declaration: inheritance is a good way for keeping your code nice and tidy, but if you use it the wrong way, you get a headache.
Lets say we run a bakery. Currently we are creating only 3 products: bread, rolls and cakes. Each product is created in 3 stages: make dough, bake and wrap. It is pretty straightforwardness to build the object hierarchy for the bakery:
- Create an interface for the product (IProduct)
- Implement 3 products (bread, roll, cake)
- Create a base class for the Factories (ProductFactory)
- Each product factory uses a template method for running the process (create dough, bake, wrap)
- Implement 3 factories, one for each product
Plain and simple. Now we can create a collection of factories, tell them to process and collect the products.
Suddenly we discover that there are 2 types of dough: sweet and salty. We think: “hey, why not create 2 abstract factories, one for sweet and one for salty”. That’s a good idea, and you fill you showed the world your supreme c# kong fu with the Abstract Factory pattern. Yes, you did it again! (and apparently, this is a good design decision).
Now your code structure looks like this:
Your bakery grows, and so is your code.Now you are supporting 20 products, each of them has its own factory, and you are repeating this process of uniting similar factories to more abstract classes that overrides a part of the process.
Now lets fast forward to a more ambitious bakery, one that produce 100,000 different products. The inheritance tree is so big and complex, and so is maintainability: almost any change is breaking something, each new product you add is just a big pain: you don’t remember which factory to use, you do your best, but the odds are against you..
Why does it happens? its is not because inheritance is evil. Actually, inheritance is good, and it will allow us to find the solution, but you are using it the wrong way. The problem is that each factory is tide to these 3 actions. Take 2 factories where 2 actions are the same and one is different – the classic inheritance will call for another base class that implements the 2 methods, and the 2 classes will implement the third. It is time for a different approach, more single responsibility principle one:
Assume each stage is a class of its own, managing only one granular action, for example “Bake()”: We will have the interface IBake, and we will derive classes from it, implementing different baking actions. The same goes with “make dough” and “wrap”. Now, each factory will instantiate the relevant actions, creating all its parts (instantiating dough maker, baker and wrapper) and call them:
What are we gaining here? We gain the ability to create new factories with new combinations of the basic actions. We can also create new dough makers/bakers/wrappers without breaking the existing code. In other words, we are implementing the open/closed principle, meaning “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”. Now scaling is more reasonable.
I added some code that demonstrates this concept, you can grab it here.