DEV Community

loading...
Cover image for Why Java interfaces aren't terrible (just strict)

Why Java interfaces aren't terrible (just strict)

Bertil Muth
Agile coach and developer
・2 min read

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.

Discussion (19)

Collapse
thomasjunkos profile image
Thomas Junkツ • Edited

I think, this only might be a problem when you never worked (weren't socialized) on a statically typed language.

I love the metaphor of interfaces being contracts. In implementing an interface you promise to syntactically adhere to the standards required by the contract - if you do semantically useful things is on another sheet of paper.

Unless you aren't implementing an interface, there is good reason for the compiler to suspect something errorneous and scream about it.

But beware of cargocultig interfaces for every class to write. I've seen many codebases where each and every class had its interface which is utterly nonsensical. Here too helps the contracts metaphpor:
Does it make sense to set up a contract if you are the only participant?
No. And before someon comes but with »but in the future« just let me say »Nah! YAGNI«

P.S.: I was socialized on statically typed languages but at present write only python and JS (plain no TS). I am missing nothing so far. But done my homework in statically typed languages helps me avoiding problems in the dynamically typed world.

Collapse
bertilmuth profile image
Bertil Muth Author

Yeah, I had a short period where I created interfaces for everything as well. I am glad it‘s over. :) I find them most useful when communicating with external systems (where external could mean sth like microservice as well).

Collapse
thomasjunkos profile image
Thomas Junkツ • Edited

I am really interested what the root for this antipattern is. I have the suspicion that once people misunderstood "program to interfaces not implementations" and took it literally.

When seeing this pattern I tell programmers that every time they do this a little puppy drowns 😬

Thread Thread
simonhaisz profile image
simonhaisz

I'm curious, do you use dependency injection and unit test heavily?

I've gotten into the pattern of creating interfaces for almost all of my classes so that except for the code that needs to construct the object everything can refer to the interface. This makes mocking out that dependency WAY easier with better test performance to boot.

Thread Thread
hurric9000 profile image
Hurric

Agree with this. It's not just about communicating with external systems but testing would be a lot easier if dependencies are passed in and we don't create new instances that depend on other classes

Thread Thread
bertilmuth profile image
Bertil Muth Author

Well, for me, a unit isn’t always equal to a class. In other words: I test several classes together. I know people have strong opinions about isolation- for me, it works just fine, and there are several TDD practioners who advocate this style (among them Martin Fowler and Kent Beck, if I‘m not mistaken).

Thread Thread
bertilmuth profile image
Bertil Muth Author

You get fewer tests, that don’t break easily when the implementation changes. The downside is errors are a bit more difficult to find.

Thread Thread
simonhaisz profile image
simonhaisz

That's a fair approach. I'm certainly not dogmatic about this stuff; as long as you have a good understanding of the pros and cons involved and accept them, like you do here, that's fine. I do think this takes a bit more discipline around understanding what the "unit under test" is, so adoption could be an issue depending your team.

I don't recall reading anything by Fowler on this specific subject and I ❤️ that man. You wouldn't happen to know the book/blog/talk off hand would you? Sometimes searching for these things can prove to be difficult.

Thread Thread
bertilmuth profile image
Bertil Muth Author

I found a pretty lengthy explanation of Robert Martin, a.k.a. Uncle Bob, who also prefers to test several classes at once if necessary: blog.cleancoder.com/uncle-bob/2017...

Thread Thread
simonhaisz profile image
simonhaisz

Thanks for the Sunday reading!

Thread Thread
bertilmuth profile image
Bertil Muth Author

There’s more :) Here’s the interview of Martin Fowler, Kent Beck and DHH about DHHs article “TDD is dead” where they mention that they don’t mock that much. martinfowler.com/articles/is-tdd-d...

Collapse
aminmansuri profile image
hidden_dude

Re: your comment about statically typed languages.

The current trend in Education is to start with Python. But as someone involved in University education myself, I wonder if starting with Python makes it harder to then later motivate people to learn this stuff. If you've done Java first, then learning Python is a breeze. But if you've learned Python first, then learning Java is a total drag. (Presumably learning the first language is always a bit problematic for some)

I wonder how we motivate students to bother with statically typed languages (Java and C# are super important in the comercial world) if we start with something so simple as Python.

Collapse
thomasjunkos profile image
Thomas Junkツ • Edited

From my personal experience I made the switch from .Net(later Java) to Python and I worked with a legacy codebase where I saw patterns which were hard to grasp and a compiler would have definitivly complained.

OTOH I would like to see Go more in education. Although I dislike the language for $reasons, I clearly see the upsides of being elegant, fast to learn, general purpose and a nice take on structs and interfaces which mostly makes the student fall into the pit of success.

But I am going too offtopic 😉

Thread Thread
aminmansuri profile image
hidden_dude

I know a lot of people are currently interested in Go. But from an educator's perspective I see little benefit in promoting it at this time. While it has some following in the commercial world its not huge (yet?). Check indeed.com for example and compare to others.

Also, from a linguistic perspective I think some of Go's omissions are important. I think students should be exposed to a full set of OO features. While Go may have its niche in the commercial world, I think a well grounded Python+Java+C developer should be able to pick it up.

Collapse
quii profile image
Chris James • Edited

In fairness though, the author of the other article is comparing it to another statically typed language (Go).

Interfaces in Go are way more convenient to use compared to Java whilst still being statically checked.

Collapse
forstmeier profile image
John Forstmeier • Edited

Well said and I think this is the actual approach I'll be taking; I was able to build a solution using the Mockito library but I was really interested in a "native" solution and I like this one!

Collapse
simonhaisz profile image
simonhaisz

Looks like Amazon skipped the I in SOLID.

Collapse
jakebman profile image
jakebman

The author of the other post wrote DynamoImpl interface in their own project (because the AmazonDynamoDB had interfaces they didn't intend to use) and expected the AmazonDynamoDB to automagically implement DynamoImpl because the signatures match. (This is what Go does, and the author was dissatisfied that Java did not do this.)

If Amazon can have their Java classes implement interfaces that were created after they ship their production artifacts, I'm going to start expecting my prime deliveries a lot faster

Collapse
simonhaisz profile image
simonhaisz

My comment wasn't about what the guy in the other article was trying to do. It was about how this interface has dozens methods. It should have either implemented 4-5 other more focused interfaces or, preferably in my opinion, used composition to own those 4-5 interfaces.

If felt with too many of the God classes and I know how they come about. Wether it's a DB or a session is the core of the context of the application so it ends up growing like Katamari.