DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Using Java Class Extension Library for Data-Oriented Programming - Part 2
  • Configurable Feign Client Retry With Reusable Library and DRY
  • Using Java Class Extension Library for Data-Oriented Programming
  • Java 23: What Developers Need to Know

Trending

  • Caching 101: Theory, Algorithms, Tools, and Best Practices
  • ITBench, Part 1: Next-Gen Benchmarking for IT Automation Evaluation
  • Data Lake vs. Warehouse vs. Lakehouse vs. Mart: Choosing the Right Architecture for Your Business
  • Next Evolution in Integration: Architecting With Intent Using Model Context Protocol
  1. DZone
  2. Coding
  3. Java
  4. Collaborators and Libraries: Java Design Patterns for Success

Collaborators and Libraries: Java Design Patterns for Success

Should constructors throw exceptions? What can the 1815 Congress of Vienna teach us about good Java design?

By 
Alan Hohn user avatar
Alan Hohn
·
Apr. 26, 16 · Opinion
Likes (7)
Comment
Save
Tweet
Share
10.3K Views

Join the DZone community and get the full member experience.

Join For Free

My fellow Zone Leader Sam Atkinson wrote an excellent article on Beautiful Constructors. While I definitely agree that the constructors in his article are beautiful, I wasn't as sure that his prescriptions could be universally applied. He graciously allowed me to use his piece as a springboard for a counterpoint, in the hope of some good discussion. Of course, the opinions in this article are my own.

Collaborators

The style of design Sam describes seems to me to work best for a collaborator class. I'm not as convinced it's the right approach for a library class. Here's what I mean.

In any well-designed, modular program, we have more structure than just "a bunch of objects that call 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 larger groups reach outside the group and interact with other groups. This concept is called "low coupling, high cohesion" or controlling "interface width".

At its best, this design style means that even in a large application, we have just 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 whole behavior of the module, hiding the implementation details. The whole thing is like the interaction of the Great Powers in the Concert of Europe: each collaborator agrees to stay out of the internal affairs of the others and to respect their territory.

Collaborators shouldn't expose their internal state to other collaborators; it's their job to keep that hidden. Also, collaborators should not be handling possible "null" values for other collaborators, because knowing what to do in the case some other collaborator is missing typically requires knowing a lot about what that collaborator does, which breaks encapsulation. Finally, collaborators should not generally 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. Great Powers suppress their own rebellions.

So I am definitely a fan of simple constructors for a collaborator (with a simple non-null assertion so we fail fast):

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 non-null assertion above is from Spring; if you're not already using Spring don't bring it in just for that assertion. (As an aside, I think that bringing in a chain of dependencies to get one simple function should forever be known as "leftpadding".)

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

Library Classes

By contrast, I don't think this style of constructor is always the right one for library classes. By library class, I mean a class that may be an entry point to multiple classes with related functionality, but a class that is not at the level of a collaborator because it serves too specific of a purpose. Often this purpose is technology-specific rather than application-specific. Some examples will help to illustrate the difference.

When I saw Sam's suggestion to avoid complex constructors and instead have a separate init method, one counter-example that jumped into my head is that well-known library class from Java network programming, java.net.Socket.

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


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

To me this makes perfect sense semantically, and thinking about "why" helped me to understand what I think is the important difference between collaborators and library classes. By throwing an exception from the constructor, this class is saying:

  • I exist solely to wrap a socket connection
  • If I don't connect successfully, you don't have a "Socket"

This seems like exactly the right set of semantics for a socket class. It is totally different from a collaborator that should be created early and kept around a long time. I only wish this class went one step further and eliminated the "unconnected" constructors and the "connect" public method. A better example might be FileOutputStream:

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


This class does not have a 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 make a new instance. That seems exactly right because it naturally leads the user to the right behavior, which is to keep this object around only for as long as it is needed to write to the file. So this class is implicitly saying:

  • I exist solely 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 cannot be used and should be discarded

That's some impressive semantic richness that we get just by throwing exceptions from the constructor and not having a public "init" or "open" method. Note that all of this is in the Javadoc, but we didn't need the Javadoc, because we could infer it from the available methods and their signatures. That is the best kind of documentation.

Null Values

So how about null values? Can I make a case for using them sometimes? Possibly. I really, really like the design pattern that Sam suggests, which is to have a no-op implementation of the interface that can be used as a "default" by users of the class that 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 lead users to think they have a transaction when they don't have one. And often, what we require is not a transaction but a TransactionManager; not a connection but a ConnectionFactory. I'm not sure what a no-op implementation of either of those would look like.

This gets back to the distinction I made between collaborators and library classes. A collaborator has to combine behavior from many things, but it does it in the context of one 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 wind up writing code that says, "I'll use a transaction manager if you give me one, but I won't fail if you don't".

Conclusion

Hopefully I've added to the conversation here. I think there's a lot of value in thinking about topics like this, so I'm very grateful to Sam for his article and I'm looking forward to reading his next, whether in reply to this one or on some other topic. Mostly, I think as developers we need to cultivate an aesthetic sense of what good design is, because it makes it easier to "see" good design when we don't have to stop to reason through it at each step. But the only way to cultivate that aesthetic sense of design is to make arguments about "what makes design good" and then try to back it up with examples.

Collaborator (software) Library Design Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Using Java Class Extension Library for Data-Oriented Programming - Part 2
  • Configurable Feign Client Retry With Reusable Library and DRY
  • Using Java Class Extension Library for Data-Oriented Programming
  • Java 23: What Developers Need to Know

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!