No Class is an Island
One of the biggest challenges I’ve found with any framework is to make it self-discoverable.
It is often difficult to build a framework or API in a way that users of that framework can easily know what exists and when to use what.
One of the main reasons why it is difficult to write a good framework is that many developers tend to create classes that are very loosely connected to other classes in that framework with which they are intended to interact.
Defining the island
When you create a series of classes, do you explicitly plan for those classes to depend on each other or do you try to design them in a way so that they are independent?
This question brings up the old topic of loose coupling and tight cohesion.
I consider a class to be an island if it is loosely coupled, but also loosely cohesive.
A class that is an island is a class that does its own thing with very little dependencies or very weak dependencies.
Imagine for a moment a CurrencyConverter class.
This class is designed to take an amount of one currency and convert it to another currency.
There are several ways we could create the API for this class.
Here is one of them:
Now this seems like a pretty reasonable API, but there is a problem here. The problem is if you have a class that implements ICurrency, you don’t have any good way to know that this class exists.
If we don’t do anything else, this class might as well not exist at all. It is an island.
It can see classes it needs to use, but the classes that could benefit from CurrencyConverter don’t know it exists.
The developer that is using an ICurrency implementation has no good way to know that this convert exists. Most likely a developer will end up writing their own implementation of a currency converter.
The date and time link
You will often see this in code bases and frameworks. You will have many different utility folders or classes that help do date and time operations.
The problem is that no one really knows these utility classes exist and so everyone ends up rewriting the functionality all over your code base and you have a mess.
Take a look at your own project, see if you can find all the utility classes that deal with date, time or currency. Now check to see if those utility classes are being used everywhere they could be. Chances are they are not.
We need to build a bridge to the island so that everyone can enjoy the nice beaches.
No point building your wonderful CurrencyConverter if no one knows it exists, right?
So how do we solve this problem?
It definitely is a tricky problem to solve.
The best way I have found is to tie the dependent class back to its dependency.
Yes, I am advocating circular dependencies!
Don’t be alarmed, it is not that bad. All you have to do is make sure that your ICurrency interface has a reference to CurrencyConverter so that someone using ICurrency will know it exists.
Now there are many ways of doing this. Some involve using a base Currency class, others involve creating different static constructors for Currency classes.
I am going to show you a very simple example, just to make my point.
It really is that simple. You have now built a bridge to the island that was CurrencyConverter.
Now when a developer types ‘.’ on an ICurrency implementation they will see a convert method that uses your converter and they will know it exists! Joy!
Again, there are many ways you could build this bridge. My intention is not to debate them here.
The point is… BUILD THE BRIDGE!
But it is a circular dependency, that is bad
It is worse than 5 implementations of currency conversion in your code base?
What we have really done here is build something that is extremely tightly cohesive. This is not necessarily a bad thing.
What we don’t want to do is to tightly couple the currency conversion to our billing system code. We don’t want to have some building system class being used by our CurrencyConverter.
Applying the idea further
The basic idea here is that every time you create a new class you should think about how someone will know it exists.
If the class is out there on its own and it is likely to be useful in the the future, you must do something to tether it back to its dependencies.
It is helpful to have the attitude that if a developer can’t discover your class by some other class through intelli-sense, your class might as well not exist.
Every time I create a class, I try to think of two things:
- How will someone use this class
- How will they know this class exists