Why Java interfaces aren't terrible (just strict)

Bertil Muth - Nov 2 '18 - - Dev Community

A recent article described some problems when dealing with Java interfaces, coming from a language that allows duck typing.

The bottom line is this. There is an interface:

public interface DynamoImpl {
  public PutItemResult putItem(PutItemRequest putItemRequest);

  public GetItemResult getItem(GetItemRequest getItemRequest);
}
Enter fullscreen mode Exit fullscreen mode

And there is a class AmazonDynamoDB. That class has the two above methods, but also a lot more. Now John, the programmer, wants to expose only these two methods of AmazonDynamoDB to the rest of his program. He wants to use them in his class Database:

public class Database {
    public DynamoImpl db;

    // code removed for simplicity

    public Database(DynamoImpl db) {
        this.db = db;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now: in a language that supports duck typing, you could just pass an instance of class AmazonDynamoDB to the constructor, and call the two methods (because the class has them). Not in Java, though. You need to pass in an instance of a class that literally implements the DynamoImpl interface (or extends a class that does so). But AmazonDynamoDB does not implement that interface.

The easiest way to achieve the same goal in Java is to create a wrapper that delegates to an AmazonDynamoDB instance:

public class AmazonDynamoDbWrapper implements DynamoImpl{

    private AmazonDynamoDB amazonDynamoDB;

    public AmazonDynamoDbWrapper(AmazonDynamoDB amazonDynamoDB) {
        this.amazonDynamoDB = amazonDynamoDB;
    }

    @Override
    public PutItemResult putItem(PutItemRequest putItemRequest) {
        return amazonDynamoDB.putItem(putItemRequest);
    }

    @Override
    public GetItemResult getItem(GetItemRequest getItemRequest) {
        return amazonDynamoDB.getItem(getItemRequest);
    }

}
Enter fullscreen mode Exit fullscreen mode

Yes, a bit verbose, but pretty straight forward once you understand the pattern. Now you can pass an instance of this class to an instance of Database.

So much for the production code. What about tests?

The easiest solution is to provide a minimal implementation of the DynamoImpl interface, like so:

public class StubbedDynamoImpl implements DynamoImpl {

    @Override
    public PutItemResult putItem(PutItemRequest putItemRequest) {
        return new PutItemResult(/* Test data here */);
    }

    @Override
    public GetItemResult getItem(GetItemRequest getItemRequest) {
        return new GetItemResult(/* Test data here */);
    }
}
Enter fullscreen mode Exit fullscreen mode

Again, you can pass an instance of this class to the Database instance's constructor, in a test:

public class DatabaseTest {

    @Test
    public void createsDatabase() {
        Database database = new Database(new StubbedDynamoImpl());
        // Whatever your test assertions are, here
    }
    // ...
}
Enter fullscreen mode Exit fullscreen mode

No need for fancy mocking frameworks if all you want to expose is two methods.
This is the easiest solution I could think of. I hope it's useful.

P.S.
One word concerning Java conventions: please don't use the suffix Impl for interfaces. This should be used for concrete implementation classes (if at all), or otherwise you might confuse a lot of people.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .