Should We Use Interfaces?
There is lots of information on the internet, in forums, books and at conferences that suggest we should be using interfaces when designing our code. One of the most popular programming books – Design Patterns by the Group of Four – shows you how the use of interfaces in your code will help to improve the design. One of the main reasons interfaces are lauded so much, by experts in the field, is the ability to change the code in the future. Adding interfaces upfront means that at a later date, when the requirements have inevitably changed, it is much easier to swap out the implementation with another, and therefore save you some headache inducing refactoring. As the author François de La Rochefoucauld stated:
The only constant thing in life is change.
So if interfaces are so great, why don’t we have an interface for every class that we create? Well, some may argue that you should. Some developers write an interface for each class and then pass the interface around instead of passing the concrete class. This leaves the code very loosely coupled and easy to manipulate. Doesn’t that sound wonderful?
Well, maybe not so much. You see, there is also contradicting information on the internet, in forums, books and at conferences that states that you should only add interfaces when required. YAGNI (i.e., you ain’t gonna need it) states that there is no point writing an interface for every class because it is easy enough, with today’s IDEs, to automatically generate an interface when required. This principle is based on the idea that good development involves heavy use of refactoring to keep the code clean. Bob Martin (AKA Uncle Bob) has this saying about coding (taken from the boy scouts):
Always leave the campground cleaner than you found it.
So, if we are constantly making small changes to clean the code as we go along, then adding a new interface when required isn’t that big of a deal, right? Another point that gets raised when there are lots of interfaces being used is how much more difficult it is to navigate through the code to understand what is going on. However, this for me, is where understanding how your IDE works pays dividends. In Eclipse, you can click F3 to drill into a class. When you hit an interface, you can hit F4 and see the implementations in the Type Hierarchy and double-click to drill into it. Once you have become adept at using these keys, understanding the code becomes quick and easy again.
Unit Testing Using Fake Objects and Interfaces
Writing unit tests is a skill, like all programming, that needs to be learned and mastered. Good code design helps with writing unit tests, which is why TDD has become so popular. If you write the test first and then write the code to make the test work, then the design you end up with, in theory, is well designed and easy to maintain in the future. One of the tools I generally use when writing unit tests is the notion of fake objects. These are objects that pretend to be an object we care about but allow us to call our own faked methods instead of calling the real methods. This means we can setup the fake object to return the values we expect within our class. Take the following example:
We are testing a CashMachine
class, which gets the required bank from a BankDirectory
class. It then uses this to withdraw the money. Here’s what this may look like:
public class CashMachine {
private BankDirectory bankDirectory;
public String withdraw(Card card, int amount) {
Bank bank = bankDirectory.getBank(card.getBankId());
UserCredentials credentials = card.getCredentials();
boolean withdrawSuccess = bank.withdraw(credentials, amount);
if (withdrawSuccess) {
payOut(amount);
return "SUCCESS";
}
return "ERROR";
}
}
Before I continue, it’s probably a good idea to highlight that the getBank(String)
method of BankDirectory
access a database to retrieve that required bank. As this is an external system and not one that is suitable for a unit test, we need a way to override the method so that it returns the Bank
that we want to use.
There are a few different schools of thought on how to test code that involves collaborators that access external systems. Martin Fowler can explain this much better than I can, so if you want more information, then check out: Mocks Aren't Stubs. However, I will concentrate on the use of fake objects for this post as it has helped me to understand not only a reason to use interfaces but how not using them can cause headaches.
So, the test that I am going to show in this example is this:
GIVEN bank approves withdrawal
THEN return "SUCCESS"
For this test, I need to create 2 fake objects: A FakeBankDirectory
and a FakeBank
.
public class FakeBankDirectory extends BankDirectory {
private Map<String, Bank> banks;
public FakeBankDirectory() {
banks = new HashMap<String, bank>();
}
@Override
public Bank getBank(String id) {
return banks.get(id);
}
public void putBank(String id, Bank bank) {
banks.put(id, bank);
}
}
public class FakeBank extends Bank {
private boolean acceptWithdrawal = false;
@Override
public boolean withdraw(UserCredentials creds, int amount) {
return acceptWithdrawal;
}
public void setWithdrawalAccepted() {
acceptWithdrawal = true;
}
}
As you can see, FakeBankDirectory
extends BankDirectory
and FakeBank
extends Bank
. This means that in my test, I can swap my collaborators for the fakes and not worry about actually calling the real objects. The two methods that I need to control have been overridden which means I should be able to create my test like so:
public class CashMachineWithdrawTest {
@Test
public void givenWithdrawSucceeds_thenReturnSuccess() {
FakeBankDirectory bankDir = new FakeBankDirectory();
Card card = new Card(BANK_ID);
FakeBank bank = new FakeBank();
bankDir.putBank(BANK_ID, bank);
bank.setWithdrawalAccepted();
CashMachine cashMachine = new CashMachine();
cashMachine.setBankDirectory(bankDir);
String result = cashMachine.withdraw(card, 50);
Assert.assertEquals("SUCCESS", result);
}
}
At this point, I run the test and expect it to pass but instead of seeing a lovely green bar, I see the red bar! The error I get shows my test is trying to connect to an external system that the FakeBankDirectory
depends on. Some of you may already be screaming at the screen at my mistake. So it’s time to confess. I haven’t told you the whole story. I have hidden one rather significant detail. The reason why I didn’t mention it at the beginning was because sometimes developers don’t see the whole picture themselves, or don’t understand some of the little, implicit, intricacies of Java. So here is the part I missed out.
BankDirectory
has a constructor. This constructor sets up the connection to a database that it can then use when someone invokes getBank(String)
. The stacktrace
shows us that this is the external system that is trying to be called and is failing. You may be wondering how this is possible when we don’t instantiate a BankDirectory
? We have our own FakeBankDirectory
constructor and we don’t call super()
on it so how is it being called?
Some of you may already know what is happening here but I will elaborate for those that don’t. When a class extends a second class and the sub-class is instantiated, the parent class’ constructor always gets invoked first. If the parent class also extends a class then its parent gets invoked before both the sub-classes. Here is another example:
class Animal {
public Animal() {
System.out.println("Animal");
}
}
class Dog extends Animal {
public Dog() {
System.out.println("Dog");
}
}
class application {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
In this situation, when the application is run, the output is:
Animal
Dog
As explained above, the parent class’ constructor is always invoked first, even if you don’t explicitly call the super()
method like below:
class Dog extends Animal {
public Dog() {
super();
System.out.println("Dog");
}
}
This is because the Java compiler automatically adds the super()
constructor call in for you, if you leave it out. The other thing the compiler does for you is it automatically creates a default constructor if you don’t. So if Animal
didn’t have a constructor, then the compiler would create one for you and then call it when you instantiate Dog
.
If you decide to create a constructor with a parameter, then the default constructor is not created. Instead, your constructor must be used when instantiating the object.
class Animal {
public Animal(String type) {
System.out.println("This animal is type: " + type);
}
}
class Dog extends Animal {
public Dog() {
super("Dog");
System.out.println("Dog");
}
}
class application {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
In the above example, the output would be:
This animal is type: Dog
Dog
If you didn’t add the super(“Dog”)
constructor call, then the compiler would throw an exception because a super constructor always has to be called and because you have created a constructor, no default constructor is created automatically by the compiler.
Back to the CashMachine Example
So we take a step back from the Animal
example and go back to our CashMachine
example, and the point of this post, which is to highlight that if you are using fakes in your tests, you should be aware that extending real “build” classes can cause dependencies to throw errors. It would be much better if there was an interface that both the BankDirectory
and the FakeBankDirectory
implemented. This would allow the FakeBankDirectory
to stand alone but would allow us to use it in place of the BankDirectory
. So, here is the better version:
interface FinancialInstituteDirectory {
public abstract Bank getBank(String bankId);
}
class BankDirectory implements FinancialInstituteDirectory {
public Bank getBank(String bankId) {
}
}
class FakeBankDirectory implements FinancialInstituteDirectory {
public Bank getBank(String bankId) {
}
}
Our test class will look exactly the same but in this instance, because FakeBankDirectory
is implementing FinancialInstituteDirectory
and not extending BankDirectory
directly, the test will not call the BankDirectory
class’ constructor and fail with an external system dependency.