Learn Computing from the Experts | The Rheinwerk Computing Blog

SOLID Modeling in Java Programming

Written by Rheinwerk Computing | Mar 21, 2024 1:00:00 PM

If you want to write good object-oriented (OO) software, you should adhere to certain design principles. These best practices aren’t mandatory, of course, but usually improve the design.

 

Three Rules

The first three rules are:

Don’t Repeat Yourself (DRY)

Code duplication should be avoided, and duplicate code should be outsourced to methods. Furthermore, existing code—from your own libraries, from Java Platform, Standard Edition (Java SE) or open-source libraries—should be used.

Keep It Simple, Stupid (KISS)

More precisely, “keep it simple and foolproof.” A problem should be solved in a simple and easy-to-understand way. For developers, this rule means you should write simple code, maybe only few lines of code, understandable at first sight.

You Ain’t Gonna Need It (YAGNI)

This principle should remind you to write simple code and to program only what is expected to fulfill the requirement of the moment. YAGNI is central to extreme programming (XP) and the idea of “always implementing the simplest possible solution that works.” Something may be programmed that never goes live later, which is a waste of time and money, but the code still needs to be documented, maintained, and tested.

 

SOLID

Michael Feathers used the acronym SOLID to capture five points that make a good OO design. The individual criteria themselves come from various authors.

S: Single Responsibility Principle

In somewhat flippant terms, this principle stands for “Do exactly one thing, but do it right.” A type should have exactly one responsibility, so that, when changes are made, in the best case, only one place must be adjusted and not many places. The opposite is referred to as God classes, that is, classes that can do anything—an anti-pattern. Robert C. Martin, who describes the single responsibility principle in his book Agile Software Development: Principles, Patterns, and Practices, also says, “There should never be more than one reason to change a class.” So what does this principle mean in practical terms?

 

Let’s assume a person class stores a name, zip code, and age. Some requirements exist for zip codes and ages: In the US, a postal code consists only of digits and is 5 digits long. In general, an age is never a negative number and is limited and finite. However, these two validations are two different things, so a person class takes on responsibilities that in themselves have nothing to do with a person. Accordingly, the class will need to be adjusted in two places when the validation is changed; two reasons are more than one reason and consequently break the single responsibility principle.

14

If the modeling takes the single responsibility principle to an extreme, too many small types are created. This profusion of types doesn’t help other code developers if responsibilities are no longer understandable due to this confusion.

O: Open–Closed Principle

Bertrand Meyer formulated in his 1988 book Object-Oriented Software Construction that modules must be both open (for extensions) and closed (for modifications). Where others use the term “module,” Java developers should imagine a “type.” A conventional class is closed to modification, especially with private states, but a subclass allows new states to be added without code changes to the superclass. Additionally, methods can be overridden to customize an implementation. However, a subclass must not give methods different semantics, which would otherwise break the object’s cohesiveness.

L: Liskov Substitution Principle (LSP)

Barbara Liskov gave the 1987 lecture “Data Abstraction and Hierarchy” about the fact that replacing objects in programs with objects of a subtype should be possible without sacrificing correctness. Of course, for the replacement to work, the subtype must know what is “correct” so that methods do not realize an incorrect implementation that breaks the behavior. In other words, children must inherit and respect the behavior of their parents. This rule is not easy in Java because syntactic constructs such as preconditions, postconditions, and invariants don’t exist; developers must therefore extract what is correct behavior purely from the Javadoc (i.e., the textual information).

I: Interface Segregation Principle

The interface segregation principle is attributed to Robert Cecil Martin when he worked on the printer system for Xerox. The central statement of this principle is “Many client-specific interfaces are better than one general interface.” The client is the user of a Java type, and the term interface means the range of methods in general. In practical terms, some types have a lot of methods that thus make these types “general.” If such objects are passed around, then the program places always get the entire object with all methods. However, the complete range of methods isn’t always necessary, and sharing all the methods may even be dangerous. A better approach is to keep the application programming interface (API) small, allowing various entities only what is actually needed.

D: Dependency Inversion Principle

“Depend only on abstractions, not on specializations” is how Robert Cecil Martin put this principle. This principle is best illustrated by a layered architecture: An upper layer draws on services from a deeper layer. However, the upper layer shouldn’t cling to concrete types but instead should depend only on basic types, such as Java interfaces. In this context, the principle of programming against interfaces fits well.

The Big Picture

Like a Quentin Tarantino movie, everything is somehow connected in a grand design. However, design practices have focal points: The single responsibility principle takes on types and architecture at large. The open-closed principle is about types and their extensions. LSP is about inheritance and subtypes, and the interface segregation principle is about business logic and type dependencies.

 

Don’t Be STUPID

Every “do” in SOLID is matched by a “don’t” in STUPID. This acronym stands for the things you should avoid:

Singleton

A singleton is an object that can exist only once in the system. Such objects are always around, and they aren’t bad in themselves. However, a problem arises when many developers write the singleton itself as a class, and then an implementation is quickly created that’s difficult to test due to its global state. A better approach is to use frameworks that provide copies of objects for you to work with.

Tight Coupling

The goal of good design is to reduce dependencies; the fewer modules/packages/types a piece of code draws on, the better. Specifically, the fewer import declarations are required, the better.

Untestability

If testing isn’t thought through until after design and programming, it’s often already too late—the result then is code that’s difficult to test, especially if the coupling is too tight. A better approach is test-driven development (TDD), where testability affects the design. Designers and developers should consider in advance how a particular class and specific functionalities can be tested before moving on to intensive implementation.

Premature Optimization

Developers think they have a sense of which parts of the program eat up performance and which parts are fast. But often they’re wrong, wasting a lot of time optimizing the wrong spots. The best approach is to implement a simple solution according to KISS and then have a profiler—a software that measures the duration of code executions—show you exactly where rework is needed.

Undescriptive Naming

Variable names like one, z, l, myvariable, var1, val10, theInt, aDouble, _1bool, and button123 aren’t particularly meaningful and must be avoided. A future reader of your code should immediately understand what a variable is about.

Duplications

Code copied exactly with minor changes must be avoided. Code duplicates can be found relatively well with the available tools and plugins for various integrated development environments (IDEs).

 

Editor’s note: This post has been adapted from a section of the book Java: The Comprehensive Guide by Christian Ullenboom.