How Interfaces Are Refactoring Our Code
I’m sure you’ve heard this idea before: all the public methods of an object should come from interfaces. I very much agree with it and, if you ask me, I’ll tell you that all objects, without exception, should implement at least one interface. This is of paramount importance, especially when developing libraries and reusable components. You want to provide extensibility and testability, you want to let your users decorate or reimplement objects etc.
But what about standalone applications, where you have objects that represent things which can have only one implementation? Even those? Yes. Let’s see why, through an example.
Assume your application reads some configs from a
.yml file. You introduce the following
One argument for interface
Config would be the following: what if, later, you decide to read the config from XML? Then you would have another implementation called
ConfigXml. It’s a good reason but you won’t do it because, when the time for change comes, you will simply modify the
ConfigYml class above – it’s very unlikely that you will ever have both XML and YAML configs. I would think the same.
Still, I would introduce it for another reason: the case when the file
.config.yml is missing. Here’s how your code would look without an interface:
null if there is no config, thus complicating method
handleCommand a lot. It is inevitable. What else to return besides
null? And you have to check for it and cover the exceptional case.
However, if you had interface
Config, you would realize that it should have a
With this approach, an object of type
Missing represents the case when the config file is not there. We won’t have to return
null or do any other type of
if/else forking in our code.
We just call
command.config() to fetch the configuration, and we know that all the cases are covered. The
handleCommand method becomes much smaller (it’s still far from a perfect design, though)
Missing configuration in the above example returns false for
tweeting and an empty list of
commanders. Maybe you would like your application to throw an exception if the config is missing, so you should have the implementation
Mandatory, which throws an exception when either of the methods is called.
You see? This is why interfaces are important. If it wasn’t for the
Config interface, the code would be polluted with if/else forks for
null checking and/or for exception throwing. Also, you can easily use the decorator pattern to add more functionality: maybe you only want to use the commanders which are admins – there are dozens of possiblities which you wouldn’t see if there was no interface there.
Another way of putting it is the following: objects must do as much work for you as possible. You should tell an object to do something, rather than ask for data and manipulate it yourself. This is a core principle in OOP, which is very hard to apply when you don’t use interfaces. Objects
Mandatory handle very important things for us, relieving us of useless logic and making the code more maintainable and testable.
To conclude, just keep in mind: declare interfaces for any object, even if you don’t see a real use for it at first. You will surely see it later, when trying to use that object. Any questions?