And therein lies the crux of the problem. Dependencies.
As I was teaching about unit testing all I really needed was the class under test and any data beans it would need to get it's job done. 2 or 3 classes tops. So I start by bringing in the first dependency, then the dependencies that class has, then the dependencies those classes have. After I got to 320 classes I gave up and went with different approach.
Remember the big ball of mud I showed you previously, well there you have a living, breathing example of it.
The class I was testing was a wrapper over a 3rd party library so itself should be a fairly straightforward piece of code, so why on Earth was it reaching back into the core code so grotesquely? Because the classes it made use of were concrete dependencies.
How do we stop this from happening and get back to a nice, loosely coupled system? It can be fairly simple really; just introduce an interface here or there and inject it instead of instantiating a concrete implementation.
public void SavePersonAction {
public void savePerson(Person person) {
new PersonDao().save(person);
}
}
public class PersonDao {
public void save(Person person) {
// Db code here
}
}
Becomes...
public void SavePersonAction {
private PersonDao dao;
public SavePersonActon(PersonDao dao) {
this.dao = dao;
}
public void savePerson(Person person) {
dao.save(person);
}
}
public interface PersonDao {
public void save(Person person);
}
public class DbBasedPersonDao implements PersonDao {
public void save(Person person) {
// Db code here
}
}
Easy eh?
When you're sat there coding away and think to yourself “do I need an interface for this?”, if your code is providing a service, behaviour or access to something- in essence, anything that isn't a data object, work to an interface.
What does it cost you? Virtually nothing.
What does it give you? Plenty.
A common thought process I see is to only introduce an interface when there is the possibility of multiple implementations, and sure, you have to do it then but why stop there?
Working to an interface allows you, the developer, to specify exactly what an implementing class should do. You get to specify the contract, rather than relying on the default behaviour provided by the only implementation.
In the case study above a single well placed interface injected into the class would have enabled me to just copy 1 class and 2 interfaces instead of half the codebase. Surely that's got to be a win?
No comments:
Post a Comment