Collaborators and Libraries: Java Design Patterns for Success

My fellow Area Manager Sam Atkinson wrote an excellent article on Beautiful Constructors. While I totally agree that the builders in his article are fine, I wasn’t so sure his prescriptions could be universally applied. He graciously allowed me to use his piece as a springboard for a counterpoint, hoping for a good discussion. Of course, the opinions expressed in this article are my own.

Collaborators

The style of design described by Sam seems to me to work best for a collaborater classroom. I’m not convinced this is the right approach for a library classroom. Here’s what I mean.

In any well-designed modular program, we have more structure than just “a bunch of objects calling each other”. Classes are part of a larger group and work together within that group to accomplish some discrete function for the overall program. Ideally, there are very few points where these large groups break out of the group and interact with other groups. This concept is called “low coupling, high cohesion” or controlling “interface width”.

At best, this style of design means that even in a large application, we only have a few classes that are the entry point for each of these modules we’ve defined. These are our collaborators: as far as other modules are concerned, they represent the entire behavior of the module, hiding the implementation details. The whole thing resembles the interaction of the great powers in the Concert of Europe: each collaborator undertakes to stay out of the internal affairs of others and to respect their territory.

Collaborators must not expose their internal state to other collaborators; it’s their job to keep it hidden. Also, collaborators should not deal with possible “null” values ​​for other collaborators, because knowing what to do in the event that another collaborator misses usually requires knowing a lot about what that collaborator is doing, which breaks the encapsulation. Finally, collaborators should generally not throw exceptions from their constructor (except in the case of fatal programming errors) because they are expected to handle exceptional conditions themselves and not propagate them to other collaborators. The great powers put down their own rebellions.

So I’m definitely a fan of simple constructors for a collaborator (with a simple non-zero assertion so we quickly fail):

public class Austria {
private final Prussia prussia;
private final France france;
public Austria(Prussia prussia, France france) {
    Assert.notNull(prussia, france);
    this.prussia = prussia;
    this.france = france;
}

The above non-zero assertion is from Spring; If you’re not already using Spring, don’t introduce it just for this assertion. (As a side note, I think introducing a chain of dependencies to achieve a simple function should always be known as “leftpadding”.)

Of course, if all collaborators depend on each other, we can’t strictly use constructors, because we end up with a circular dependency problem. But in many cases, dependencies between well-designed collaborators end up being mostly acyclic because there is a natural order in the application.

Library lessons

On the other hand, I don’t think this style of constructor is always the right one for library classes. By library class, I mean a class that can be an entry point to multiple classes with related functionality, but a class that is not at a contributor level because it serves too specific a purpose. Often, this goal is technology-specific rather than application-specific. A few examples will help illustrate the difference.

When I saw Sam’s suggestion to avoid complex constructors and instead have a separate init method, a counterexample that came to mind is this well-known library class from programming java network, java.net.Socket.

client = new Socket("localhost", 12345);

Socket definitely has some complex logic in the constructor, which can throw either UnknownHostException or the most generic IOException. The constructor actually establishes the socket connection!

For me, this makes perfect sense semantically, and thinking about “why” helped me understand what I think is the important difference between collaborators and library classes. When throwing an exception from the constructor, this class says:

  • I exist only to wrap a socket connection
  • If I don’t connect successfully, you don’t have a “Socket”

This seems to be exactly the right set of semantics for a socket class. It is totally different from a collaborator who should be created early and kept long. I only wish this class would go further and eliminate the “unconnected” constructors and the public “connect” method. A better example might be FileOutputStream:

outStream = new FileOutputStream("temp.txt");

This class has no public “open” method, so the only way to get an instance of this class is to successfully open a file, and the only way to reopen a closed file is to create a new instance. This seems quite correct because it naturally leads the user to the correct behavior, which is to keep this object only as long as it takes to write to the file. So this class implicitly says:

  • I only exist to wrap an open file
  • If I can’t open the file, you don’t have a file output stream
  • Once you close me, my instance can no longer be used and must be deleted

It’s an awesome semantic richness that we get simply by throwing constructor exceptions and not having a public “init” or “open” method. Note that this is all in the Javadoc, but we didn’t need the Javadoc, as we could deduce it from the available methods and their signatures. This is the best kind of documentation.

Null values

So what about null values? Can I advocate for their use sometimes? Perhaps. I really, really like the design pattern suggested by Sam, which is to have an operationless implementation of the interface that can be used as the “default” by users of the class who don’t need the behavior.

But I don’t think it can be applied universally. I’m not sure what the “no-op” implementation of a transaction is; it seems dangerous to trick users into thinking they have a transaction when they don’t. And often what we need is not a transaction but a TransactionManager; not a connection but a ConnectionFactory. I don’t know what a no-op implementation of either would look like.

This goes back to the distinction I made between collaborators and library classes. A collaborator needs to combine the behavior of several things, but does so in the context of a single application. A library class does one thing, but it does it in the context of many different applications. With library classes, it’s pretty much inherent that you want your functionality to be usable in a wide variety of contexts, which means you end up writing code that says, “I’ll use a transaction manager if you give me one, but I won don’t fail if you don’t”.

Conclusion

I hope I’ve added to the conversation here. I think it’s very useful to think about topics like this, so I’m very grateful to Sam for his article and I look forward to reading the next one, whether in response to this one or on a other subject. Above all, I think that as developers we have to cultivate a aesthetic sense of what good design is, because it’s easier to “see” good design when we don’t have to stop and think about it at every step. But the only way to cultivate this aesthetic sense of design is to argue about “what makes design good” and then try to back it up with examples.

Abdul J. Gaspar