Implementation Patterns Part 1
Table of Contents
Download the full PDF version of the article here.
Creating software involves writing thousands of lines of code. Some of this code is a creative effort to build something new, while some parts are repetitive elements encountered frequently. As programmers, we dislike repeating the same tasks, but often do so out of necessity. In such situations, it is useful to adopt certain habits that allow us to make clear and conscious decisions, leading to readable and clearly reasoned code.
Core Laws of Programming
Following Kent Beck, here is a set of principles that cover much of the written software:
- Software code is read more often than it is written (sic!),
- There is no exact point of “done” in programming (sic!) - code is constantly evolving,
- The heart of low-level software is managing the flow of instructions and its state (variables),
- A reader must easily understand the idea and concept within the code and access implementation details (code should allow smooth transition from general vision to details and vice versa).
The Importance of Implementation Patterns
The aforementioned habits, which we can also call implementation patterns, are largely subjective decisions, although most are likely used and accepted by professional programmers. Three fundamental values behind these patterns are:
- Communication,
- Simplicity,
- Flexibility.
The Cost of Software
Every programmer involved in a large project knows that creating software is just the tip of the iceberg. A system in version 1.0 is not the end of the journey:
- It evolves over time - changes occur,
- Found errors need fixing.
The total cost is thus much greater than just the software’s fundamental functionalities. Furthermore, statistical studies show that maintenance costs far exceed development costs (on average, 30%/70%).
What is this “maintenance cost”? It’s the cost of understanding, modifying, testing, and deploying code. How often have we revisited our code (let alone someone else’s) created six months earlier, only to find we don’t understand it? Using patterns - low-level implementation patterns related to source code strategy - simplifies our lives; we know what to expect in the code and know we can easily read it.
Introducing Implementation Patterns
I would like to present several, perhaps a dozen, strategies that aid decision-making during software creation. To substantiate these proposals, I will draw upon source code from projects like:
- Spring Framework,
- Hibernate,
- Struts2,
- JDK6 source code.
Of course, it is impossible to cover these strategies in one article, so I hereby begin a series of articles dedicated to implementation patterns. Let’s get to work!
Classes
A class is such a fundamental element of object-oriented programming that it is equally challenging to establish obvious principles when defining and naming classes. There are many techniques to help identify classes during analysis, such as CRC cards or robustness diagrams. However, it is still up to us, as designers or developers, to decide how to create a new class. This is the greatest art of object-oriented programming.
In short, one can say that:
Every class should have a well-defined responsibility.
What does “well-defined” mean? A class should typically be responsible for one thing in the system. For example, a Converter
class should only perform conversion operations and not handle anything else, such as saving results to a database. How can we tell if a class violates this principle? The main sign is a large number of methods (though this is just a quantitative measure); ultimately, we should analyze the method names and their actions. If the methods are not cohesive and go beyond the class’s purpose, then responsibility is not well-defined.
A class name must be concise yet fully express the class’s purpose.
Naming a class is an attempt to balance two opposing forces - one side requiring the name to be as short and simple as possible (easier to remember and associate), and the other side demanding a precise reflection of the class’s purpose (which sometimes requires longer names). One of the most important functions of a class name is communicativity. Practically, it is better not to seek the perfect name immediately but initially use whatever comes to mind, then change it when a much better name emerges. In the era of advanced refactoring tools, changing a class name should not be too big of a problem.
In practice, strive for:
- Class names consisting of a single word (primarily for classes that are not inherited from others), as they are easier to remember. Additionally, if a class represents a client’s domain element, its name should be as close as possible to the client’s world (a term from their domain).
Another guideline worth following is:
The fewer classes in a project, the better.
Of course, this direction contradicts the rule advocating clear responsibility delineation, leading to greater class fragmentation. Here, we must act according to Einstein’s maxim: “Make everything as simple as possible, but not simpler.” These two forces need to be balanced. On one hand, create classes with clearly defined responsibilities; on the other hand, strive to limit the number of classes created. The more classes, the more names to remember, and more places to analyze, debug, test, etc.
Summarizing the above considerations:
- A class must have a well-defined and unambiguous responsibility, with all methods being consistent with that responsibility,
- A class name should be concise yet unambiguously express the class’s responsibility,
- Seek single-word names first,
- The fewer classes in a project, the better.
Case Studies
Open-source projects are excellent case studies. If you don’t know the library being discussed, it doesn’t matter much, as we will typically not analyze the functionality of these amazing tools but will take a closer look at their class structure.
Start by looking at the org.hibernate.cfg.Configuration
class from the Hibernate 3 project. The Configuration
name is simple, unambiguous, and seems to well reflect the object’s responsibility - it is a class that centralizes information related to Hibernate’s configuration.
As we look closer at the class content, we will see elements such as:
public class Configuration implements Serializable {
private static Logger log = LoggerFactory.getLogger(Configuration.class);
protected Map classes;
protected Map imports;
protected Map collections;
...
public Configuration addFile(File xmlFile) throws MappingException {
...
public Configuration addXML(String xml) throws MappingException {
...
protected void parseMappingElement(Element subelement, String name) {
...
private void parseSecurity(Element secNode) {
...
In addition to methods for storing and managing configuration, there appear methods for parsing XML files. Here, one can question the class’s responsibility’s unambiguity, as it begins to include XML file parsing responsibility. However, one could justify this decision with the principle “the fewer classes, the better.”
Another class from the same project, org.hibernate.cfg.Mapping
, raises fewer doubts - all elements related to ORM mapping are allocated to this class. The class name is simple, unambiguous, and expressive.
Now, let’s look at the JDK6. The java.text.DateFormat
class has a sharply defined responsibility - formatting dates according to regional settings (Locale). A simple and unambiguous name (requiring two words in this case).
There are many such examples, for instance:
java.util.regex.Matcher
- the name clearly expresses responsibility - a set of operations related to regular expression matching,java.net.Socket
- the name clearly and unambiguously defines the class’s purpose - representing a TCP socket and a set of operations on this socket.
Analyzing the source code of Hibernate and JDK6 projects clearly shows their preference:
- In Hibernate, it is common to break the responsibility principle in favor of reducing the number of project classes (which are numerous but often result in hard-to-manage classes with a dozen or even dozens of methods),
- In JDK6 (and earlier versions), the tendency is quite the opposite; there is a strong emphasis on sharp, unambiguous responsibility at the cost of producing a large number of classes - for example, fully handling a regular expression requires up to three classes (!).
I recommend analyzing the source codes of these projects and others (e.g., Spring Framework) as it is one of the best practical teachers of a truly good coding style.
Already, deciding what kind of class to create (what responsibility, what methods, what name) is quite a challenge. Therefore, it is worth spending an evening to analyze the pros and cons to accurately make one of the most frequently made decisions.
Of course, this is just the beginning and the tip of the iceberg. Soon, some reflections on interfaces.
(Text translated and moved from original old blog automatically by AI. May contain inaccuracies.)