Home Programming Practical Design Patterns for Java Developers

Practical Design Patterns for Java Developers

By Miroslav Wengner
ai-assist-svg-icon Book + AI Assistant
eBook + AI Assistant $33.99 $22.99
Print $41.99
Subscription $15.99 $10 p/m for three months
ai-assist-svg-icon NEW: AI Assistant (beta) Available with eBook, Print, and Subscription.
ai-assist-svg-icon NEW: AI Assistant (beta) Available with eBook, Print, and Subscription. $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime! ai-assist-svg-icon NEW: AI Assistant (beta) Available with eBook, Print, and Subscription.
What do you get with a Packt Subscription?
Gain access to our AI Assistant (beta) for an exclusive selection of 500 books, available during your subscription period. Enjoy a personalized, interactive, and narrative experience to engage with the book content on a deeper level.
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
Gain access to our AI Assistant (beta) for an exclusive selection of 500 books, available during your subscription period. Enjoy a personalized, interactive, and narrative experience to engage with the book content on a deeper level.
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Along with your eBook purchase, enjoy AI Assistant (beta) access in our online reader for a personalized, interactive reading experience.
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
ai-assist-svg-icon NEW: AI Assistant (beta) Available with eBook, Print, and Subscription. ai-assist-svg-icon NEW: AI Assistant (beta) Available with eBook, Print, and Subscription. BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime! ai-assist-svg-icon NEW: AI Assistant (beta) Available with eBook, Print, and Subscription.
eBook + AI Assistant $33.99 $22.99
Print $41.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
Gain access to our AI Assistant (beta) for an exclusive selection of 500 books, available during your subscription period. Enjoy a personalized, interactive, and narrative experience to engage with the book content on a deeper level.
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
Gain access to our AI Assistant (beta) for an exclusive selection of 500 books, available during your subscription period. Enjoy a personalized, interactive, and narrative experience to engage with the book content on a deeper level.
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Along with your eBook purchase, enjoy AI Assistant (beta) access in our online reader for a personalized, interactive reading experience.
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Chapter 1: Getting into Software Design Patterns
About this book
Design patterns are proven solutions to standard problems in software design and development, allowing you to create reusable, flexible, and maintainable code. This book enables you to upskill by understanding popular patterns to evolve into a proficient software developer. You’ll start by exploring the Java platform to understand and implement design patterns. Then, using various examples, you’ll create different types of vehicles or their parts to enable clarity in design pattern thinking, along with developing new vehicle instances using dedicated design patterns to make the process consistent. As you progress, you’ll find out how to extend vehicle functionalities and keep the code base structure and behavior clean and shiny. Concurrency plays an important role in application design, and you'll learn how to employ a such design patterns with the visualization of thread interaction. The concluding chapters will help you identify and understand anti-pattern utilization in the early stages of development to address refactoring smoothly. The book covers the use of Java 17+ features such as pattern matching, switch cases, and instances of enhancements to enable productivity. By the end of this book, you’ll have gained practical knowledge of design patterns in Java and be able to apply them to address common design problems.
Publication date:
February 2023
Publisher
Packt
Pages
266
ISBN
9781804614679

 

Getting into Software Design Patterns

Every software architect or developer often faces the challenges of structuring code – how to develop a code structure that remains sustainable, just as an artist draws their painting. This chapter will take us on a journey into writing program code. You will explore the challenges behind the structure of code and its organization. Together, we will approach the topic from an early stage described by the pillars of object-oriented programming, known as APIE. We will also review the principles of SOLID to gain clarity in understanding design patterns.

In this chapter, we will cover the following topics:

  • Code – from symbols to program
  • Examining OOP and APIE
  • Understanding the SOLID design principles
  • The significance of design patterns
  • Reviewing what challenges design patterns solve

By the end of this chapter, you will have reviewed the basic programming concepts, which will form the basis of the rest of the book.

 

Technical requirements

 

Code – from symbols to program

Human speech is fruitful, rich, colorful, and way beyond what the words themselves may express. Nouns, verbs, and adjectives for precisely expressing a moment or action can be used. In contrast, machines do not understand the complex constructions or expressions that humans are able to create.

Machine language is limited, well-defined, extremely specific, and simplified. Its goal is to provide the precise expression of intent for which it is designed. This contrasts with human language whose purpose is just communication and not necessarily with specifics.

A machine’s intent can be expressed as a defined instruction or a set of them. This means that machines understand the instructions. These instructions must be available to the machine in some form at the time of execution. Each machine normally has a set of instructions. Based on this kind of instruction set, machines can perform the required instructions, as shown here:

Figure 1.1 – A simplified instruction cycle inside the CPU (instruction is taken from memory and the result is stored)

Figure 1.1 – A simplified instruction cycle inside the CPU (instruction is taken from memory and the result is stored)

Let us explore one individual instruction. The instruction can be understood as a command given to the processor. The processor is the heart of the machine, or the center of the ordering and executing of processes. The machine may contain one or more of them. It depends on its design, but in any case, there is always one that takes the lead. For further simplification, we will only consider one – that is, consider a system that only has one central processing unit (CPU) dedicated to executing a program.

A CPU is a device that executes instructions containing a computer program. The CPU must contain such an instruction set, as shown in the previous diagram, to process the requested action.

Because instructions can take completely different forms depending on the CPU, there is no defined standard. This promotes different CPU platforms, which is not necessarily a bad thing and contributes to evolution. However, the fact remains that the instructions are not easy for people to read.

We have stated that machines can perform instruction collection, ideally as a continuous flow. The flow of instructions can be simplified as a queue in memory, where one instruction goes in and the other leaves. The CPU plays the role of an interpreter who works with this memory cyclically (as we saw in Figure 1.1). Okay, so the CPU interprets, but as the instructions are added to the memory, where do they come from, and how can such a stream be created?

Let us gather some thoughts. Machine instructions, in most cases, originate from a compiler.

What is a compiler? The compiler can be viewed as a CPU or a platform-specific program that translates text into target actions. The text we use to call the program and the result could be named machine code. The following diagram illustrates this:

Figure 1.2 – A simplified platform-specific flow from the source code through the compiler program to its resultant action

Figure 1.2 – A simplified platform-specific flow from the source code through the compiler program to its resultant action

Machine code is a low-level language that the machine understands and consists of language instructions that are processed sequentially (see Figure 1.1); the program was compiled, executed, and run.

In the case of Java, there is no machine code:

Figure 1.3 – A simplified flow for the Java program through the compiler to its platform execution

Figure 1.3 – A simplified flow for the Java program through the compiler to its platform execution

The source code is compiled by the Java compiler into bytecode. The bytecode is running a Java virtual machine (JVM) (see Figure 1.3). In this situation, the JVM plays the role of the interface between the bytecode and the actual instructions that are executed on the CPU. The JVM emulates a bytecode instruction. It does this using the just-in-time (JIT) compiler that is part of the JVM. The JIT compiler translates bytecode instructions into native processor instructions. The JVM is a platform-specific interpreter, analogous to directly compiled code (see Figure 1.2). The JVM also provides additional features such as memory management and garbage collection, which is what makes the Java platform so powerful. All these features allow developers to write code once, compile it into bytecode, and run a supported platform – known as write once, run anywhere (WORA).

In the context of the previous exploration, Java is a high-level language that is translated to a low level. Java provides a strong abstraction from the details of computer functionality. It allows programmers to create simpler programs for complex challenges.

At this point, we begin our journey of jointly exploring standardized solutions. Later in the book, we will review how to create code that is maintainable and extensible with fewer memory requirements. Together, we will discuss different types of design patterns that can help us to make our daily work understandable, transparent, and more fun.

 

Examining OOP and APIE

In the previous section, we learned how a program written in one of the high-level languages is converted into machine instructions that are processed by the CPU. The high-level language provides a framework for expressing the desired ideas by following the details of the language implementation. Such languages commonly provide many neat constructions or statements that do not limit the imagination. In object-oriented programming (OOP) language, the representation of the core carrier is presented by the concept of the object. This book focuses on the Java language. Java is a fully object-oriented language with additional features. What does object-oriented language mean exactly? In computer science, this means that the program focuses on the concept of classes, where instances of these classes represent an object. Next, we will repeat the importance of the OOP paradigm and deal with some basic concepts.

These terms can be expressed by the abbreviation of abstraction, polymorphism, inheritance, and encapsulation (APIE). The letters APIE indicate the four basic pillars of OOP languages. Let’s examine each word in a separate section in reverse order – so, EIPA. The motivation is to bring more clarity to our understanding of the concept of OOP.

Only exposing what’s required – encapsulation

The first in reverse order is encapsulation – let’s start with it. OOP languages, including Java, work with the concept of classes. Imagine that a class is a vehicle. The class provides all the fields that can be statically typed or object-specific – that is, initiated after an object is instantiated in the allocated memory. The concept is similar with respect to class or object methods. The method may belong to a class or its instance – in the considered example, to a vehicle. Any method can work over an object or class field and change the internal state of the vehicle or the field values (see Example 1.1):

public class Vehicle {
    private boolean moving;
    public void move(){
        this.moving = true;
        System.out.println("moving...");
    }
    public void stop(){
        this.moving = false;
        System.out.println("stopped...");
    }
}

We can apply encapsulation to the example of a vehicle. We imagine a real vehicle – only one. In such an imaginary vehicle, all internal elements and internal functions remain hidden from the driver. It only exposes the functionality it serves, such as the steering wheel, which the driver can control. This is the general principle of encapsulation. The state of an instance can be changed or updated through exposed methods or fields; everything else is hidden from the outside world. It is quite a good practice to use methods to modify the inner array or arrays of an instance. But we will repeat that later in this book. So far, it’s just a good hint.

Inevitable evolution – inheritance

In the previous section, an instance of an imaginary vehicle class was created. We encapsulated all the functions that should not be exposed to the driver. This means that the driver may not know how the engine works, only how to use it.

This section is devoted to the property of inheritance, which we will demonstrate in the following example. Assume that the vehicle’s engine is broken. How can we replace it? The goal is to replace the current one with a functional one. An engine that works this way may not necessarily be the same, especially if the vehicle model already has old parts that are not available on the market.

What we do is derived from all the attributes and functions needed to create a new engine. Concerning the class, the new replacement module will be a child in the class hierarchy.

Although the engine will not be a perfect replica and does not have the same unique object identifier, it will match all the parent properties.

With that, we have described the second pillar of inheritance in OOP – the ability to create a new class above the existing subclass. However, software designers should be wary of the fourth pillar, encapsulation, and any violations caused by a subclass depending on the implementation details of its superclass.

Behavior on demand – polymorphism

The third concept is polymorphism. With a little imagination, this can be understood as “many forms.” So, what does that mean here?

Given the vehicle described previously, it could be defined as the ability to perform a particular action in many ways. This would mean, in the context of a vehicle, that the movement of the other method, move, could happen differently based on the inputs or the state of the instance.

Java allows for two types of polymorphism, both of which differ in their runtime behavior. We will discuss both in detail.

Method overloading

This type is known as static polymorphism. This means that the correct method is resolved during program compilation – so, at compile time. Java provides two types of method overloads:

  • Changing the input argument type:
Figure 1.4 – Overloading the method of the Vehicle class by changing the input types

Figure 1.4 – Overloading the method of the Vehicle class by changing the input types

  • Changing the number of method arguments:
Figure 1.5 – Overloading the method of the Vehicle class by changing the number of arguments

Figure 1.5 – Overloading the method of the Vehicle class by changing the number of arguments

Now, let’s look at the second type of polymorphism.

Method overriding

This is sometimes called dynamic polymorphism. This means that the method performed is known at runtime. The overridden method is called through reference to the object instance of belongingness. Let us examine a simple example to illustrate this. Consider the Vehicle class a parent class (see Figure 1.6 and Example 1.2) with a method called move:

Figure 1.6 – The relation between the overridden move methods for the parent and child classes

Figure 1.6 – The relation between the overridden move methods for the parent and child classes

We intend to create a child class, Car, with a similar method named move. The child provides slightly different functions because the Car instance moves faster than the parent instance, Vehicle:

public class Vehicle {
    public void move(){
        System.out.println("moving...");
    }
}
public class Car extends Vehicle {
    @Override
    public void move(){
        System.out.println("moving faster.");
    }
}
Vehicle vehicle = new Car();
vehicle.move();
output: moving faster...

We will touch on this topic in more detail in Chapter 3, Working with Creational Design Patterns.

Standard features – abstraction

The last letter to cover (but the first letter in the abbreviation APIE) leads us to the hitherto unspecified pillar of abstraction. The key to this concept is the constant removal of specifics or individual details to achieve the generalization of the purpose of the object.

To get the best experience with this concept, let us get into the context with the vehicle example. We do not intend to describe a specific car model that belongs to a group of vehicles. Our goal is to define a common functionality that all types of vehicles under consideration can include in the context of our efforts. With such knowledge, we create a suitable abstraction, an abstract class that can be inherited later when constructing a particular model class (see Example 1.3).

This approach allows us to focus our efforts on generalizing and abstracting vehicle characteristics. This can have a positive impact on code reduction and reusability.

The abstraction in Java can be achieved in two ways:

  • Abstract classes with abstract methods (see Example 1.3 and Figure 1.7):
Figure 1.7 – The AbstractVehicle class with its CommonCar realizations and SportCar classes

Figure 1.7 – The AbstractVehicle class with its CommonCar realizations and SportCar classes

public abstract class AbstractVehicle {
    abstract public void move();
    public void stop(){
        System.out.println("stopped...");
    }
}
public class CommonCar extends AbstractVehicle{
    @Override
    public void move() {
        System.out.println("move slow...");
    }
}
public class SportCar extends AbstractVehicle{
    @Override
    public void move() {
        System.out.println("move fast...");
    }
}
  • Using interfaces (see Example 1.4 and Figure 1.8) with a generic abstract method:
Figure 1.8 – The abstraction concept achieved by using interfaces

Figure 1.8 – The abstraction concept achieved by using interfaces

public interface VehicleInterface {
    void move();
}
public class Truck implements VehicleInterface{
    @Override
    public void move() {
        System.out.println("truck moves...");
    }
}
public class Bus implements VehicleInterface{
    @Override
    public void move() {
        System.out.println("bus moves...");
    }
}

Both concepts of abstraction can be combined (see Figure 1.9):

Figure 1.9 – A combination of both abstraction concepts

Figure 1.9 – A combination of both abstraction concepts

Abstract classes and interfaces have their place in the design of code structure. Their use depends on demand, but both have a very positive impact on code maintainability and help in the use of design patterns.

Gluing parts to APIE

The motivation for each of the pillars mentioned in the previous sections is to introduce structure into the code through a given set of concepts. The pillars are defined and complementary. Let’s just examine one unit, the Vehicle class, and its instance. Instance logic and data are encapsulated and exposed through methods to the outside world. Vehicle characteristics can be inherited so that a new vehicle design, such as a new model, can be specified. Exposed methods can provide model-based behavior and incoming arguments with internal instance state changes. When crystalizing thoughts about a new vehicle, we can always generalize its behavior and extract it using an abstract class or interface.

Let us examine the generalization process over the Vehicle class development. When preparing to define a new vehicle model, we can always generalize its characteristics and extract it using an abstract class or interface. Let’s look at the following diagram:

Figure 1.10 – APIE viewed as a continual improvement process

Figure 1.10 – APIE viewed as a continual improvement process

Although these four pillars seem trivial, it is incredibly difficult to follow them, as we will continue to show in the following sections and chapters.

So far in this section, we learned about the four basic pillars of OOP and examined how these principles affect code design. Next, we will learn more about sustainable code design concepts. Let us roll on to the following section.

 

Understanding the SOLID design principles

In the previous sections, the idea of structured work was introduced. The development pillars of APIE were elaborated on in detail using examples. You have gained a foundational understanding of the concept of class instances in terms of object-oriented principles and how we can create different types of specific objects:

Figure 1.11 – Vehicle N, where N is a positive integer number, represents an instance of the Vehicle class

Figure 1.11 – Vehicle N, where N is a positive integer number, represents an instance of the Vehicle class

Classes can be instantiated so that an instance becomes an object. The object must fit into free memory. We say that the object allocates memory space. When Java is considered, allocated memory is virtual space inside the physical system’s memory.

Just a small note – we previously discussed the existence of the JVM, an interpreter of compiled bytecode for the required platform (see Figure 1.3). We mentioned other JVM features, one of which is memory management. In other words, the JVM assumes responsibility for allocating virtual memory space. This virtual memory space can be used to allocate an instance of a class. This virtual memory and its fragmentation are taken care of by the JVM and an unused object cleans up the selected garbage collection algorithm, but this is beyond the scope of this book and would be the subject of further study (see Reference 1).

Every programmer, although it may not be obvious at first glance, plays the role of a software designer. The programmer creates the code by writing it. The code carries an idea that is semantically transformed into action depending on the text entered.

Over time, software development has gone through many phases and many articles have been written and published on software maintenance and reusability. One of the milestones in software development may be considered the year 2000 when Robert C. Martin published his paper on Design Principles and Design Patterns (see Reference 2). The paper reviews and examines techniques in the design and implementation of software development. These techniques were later simplified in 2004 into the mnemonic acronym SOLID.

The goal of the SOLID principles is to help software designers make software and its structure more sustainable, reusable, and extensible. In the following sections, we will examine each of the individual terms hidden after the initial letter in the abbreviation SOLID.

The single-responsibility principle (SRP) – the engine is just an engine

The first principle is a well-defined class goal. We can say that each class should have only one reason to exist. As in, it has the intention and responsibility for only one part of the functionality. The class should encapsulate this part of the program. Let’s put this in the context of an example. Imagine the previous example of a vehicle and its abstraction. We are now extending this class with the Engine and VehicleComputer classes, as shown:

Figure 1.12 – The Vehicle class instance using Engine and VehicleComputer realization but an engine functionality does not interfere with the lights

Figure 1.12 – The Vehicle class instance using Engine and VehicleComputer realization but an engine functionality does not interfere with the lights

The engine can start and stop, but the instance of the Engine class cannot control vehicle lights, for example. The light control is the responsibility of the vehicle computer class instance.

The open-closed principle (OCP)

This principle states that the class or entity under consideration should be open to extension but closed to modifications. It goes hand in hand with the concepts already mentioned. Let’s put this in the context of an example where we consider the Car and Truck classes. Both classes inherit the Vehicle interface. Both believe that vehicle entities have a move method.

By not thinking about proper abstraction and without respecting the OCP, code can easily bear unexpected difficulties when classes are not easy to reuse or cannot be handled (see Example 1.5):

public interface Vehicle {}
public class Car implements Vehicle{
    public void move(){}
}
public class Truck implements Vehicle {
    public void move(){}
}
-- usage --
List<Vehicle> vehicles = Arrays.asList(new Truck(), new 
    Car());
vehicles.get(0).move() // ERROR, NOT POSISBLE!

The correction of the example at hand is very trivial in this case (see Example 1.6):

public interface Vehicle {
    void move();    // CORRECTION!
}
--- usage ---
List<Vehicle> vehicles = Arrays.asList(new Truck(), new 
    Car());
vehicles.get(0).move() // CONGRATULATION, ALL WORKS!

Obviously, as code evolves, non-compliance leads to unexpected challenges.

The Liskov Substitution Principle (LSP) – substitutability of classes

The previous sections dealt with inheritance and abstraction as two of the key pillars of OOP. It will come as no surprise to those of you who have read carefully that, given the class hierarchy of parent-child relationships, a child may be replaced or represented by its parent and vice versa (see Example 1.7). Let us look at the example of CarWash, where you can wash any vehicle:

public interface Vehicle {
    void move();
}
public class CarWash {
    public void wash(Vehicle vehicle){}      
}
public class Car implements Vehicle{
    public void move(){}
}
public class SportCar extends Car {}
--- usage ---
CarWash carWash = new CarWash();
carWash.wash(new Car());
carWash.wash(new SportCar());

This means that classes of a similar type can act analogously and replace the original class. This statement was first mentioned during a keynote address by Barbara Liskov in 1988 (see Reference 3). The conference focused on data abstraction and hierarchy. The statement was based on the idea of substitutability of class instances and interface segregation. Let’s look at interface segregation next.

The interface segregation principle (ISP)

This principle states that no instance of a class should be forced to depend on methods that are not used or in their abstractions. It also provides instructions on how to structure interfaces or abstract classes. In other words, it controls how to divide the intended methods into smaller, more specific entities. The client could use these entities transparently. To point out a malicious implementation, consider Car and Bike as children of the Vehicle interface, which shares all the abstract methods (see Example 1.8):

public interface Vehicle {
    void setMove(boolean moving);
    boolean engineOn();
    boolean pedalsMove();
}
public class Bike implements Vehicle{
    ...
    public boolean engineOn() {
        throw new IllegalStateException("not supported");
    }
    ...
}
public class Car implements Vehicle {
    ...
    public boolean pedalsMove() {
        throw new IllegalStateException("not supported");
    }
}
--- usage ---
private static void printIsMoving(Vehicle v) {
    if (v instanceof Car) { 
        System.out.println(v.engineOn());}
    if(v instanceof Bike) 
        {System.out.println(v.pedalsMove());}
}

Some of you with a keen eye will already notice that such a software design direction negatively involves software flexibility through unnecessary actions that need to be considered (such as exceptions). The remedy is based on compliance with the ISP in a very transparent way. Consider two additional interfaces, HasEngine and HasPedals, with their respective functions (see Example 1.9). This step forces the printIsMoving method to overload. The entire code becomes transparent to the client and does not require any special treatment to ensure code stability, with exceptions as an example (as seen in Example 1.8):

public interface Vehicle {
    void setMove(boolean moving);
}
public interface HasEngine {
    boolean engineOn();
}
public interface HasPedals {
    boolean pedalsMove();
}
public class Bike implements HasPedals, Vehicle {...}
public class Car implements HasEngine, Vehicle {...}
--- usage --- 
private static void printIsMoving(Vehicle v){
    // no access to internal state
}
private static void printIsMoving(Car c) {
    System.out.println(c.engineOn());
}
private static void printIsMoving(Bike b) {
    System.out.println(b.pedalsMove());
}

Two interfaces, HasEngine and HasPedals, are introduced, which enforce method code overload and transparency.

The dependency inversion principle (DIP)

Every programmer, or rather software designer, will face the challenge of hierarchical class composition throughout their careers. The following DIP is a remarkably simple guide on how to approach it.

The principle suggests that a low-level class should not know about high-level classes. In the opposite direction, this means that the high-level classes, the classes that are above, should have no information about the basic classes at lower levels (see Example 1.10, with the SportCar class):

public interface Vehicle {}
public class Car implements Vehicle{}
public class SportCar extends Car {}
public class Truck implements Vehicle {}
public class Bus implements Vehicle {}
public class Garage {
    private List<Vehicle> parkingSpots = new ArrayList<>();
    public void park(Vehicle vehicle){
        parkingSpots.add(vehicle);
    }
}

It also means that the implementation of a particular functionality should not depend on specific classes, but rather on their abstractions (see Example 1.10, with the Garage class).

 

Significance of design patterns

The previous sections introduced two complementary approaches to software design – APIE and SOLID concepts. It has begun to crystallize that having code in a transparent form can be beneficial for a variety of reasons, because every programmer often, if not always, faces the challenge of designing a piece of code that extends or modifies existing ones.

One wise man once said, “The way to Hell is the path of continual technical debt ignorance....” Anything that slows down or prevents the development of applications can be considered a technical debt. Translated into a programming language, this would mean that even a small part matters, if not now, then later. It also follows that code readability and purpose are crucial to application logic, as it is possible to verify various hypotheses (for example, application operation).

The inability to perform business-oriented application testing can be considered the first sign of incorrect development trends. It may appear to require the use of different mock-up techniques during verification. This approach can easily turn into providing false-positive results. This can usually be caused by the clutter of the code structure, which forces programmers to use mocks.

Although the SOLID and APIE concepts suggest several principles, they still do not guarantee that the project code base will not start to rot. Adherence to these principles makes it difficult, but there is still room because not all concepts provide the required framework for dealing with rot.

There may be long stories of how software can rot over time, but one fact that remains is that there is a cure for avoiding it or letting it go. The cure is covered by an idea called design patterns. The idea of a design pattern not only covers the readability of the code base and its purpose but also advances the ability to verify required business hypotheses.

What are the ideas behind defining it to get more clarity? The design pattern idea can be described as a set of reusable coding approaches that solve the most common problems encountered during application development. These approaches are in line with the previously mentioned APIE or SOLID concepts and have an incredibly positive impact on bringing transparency, readability, and testability to the development path. Simply put, the idea of design patterns provides a framework for accessing common challenges in software design.

 

Reviewing what challenges design patterns solve

Take a deep breath and think about the motivation for writing the program. The program is written in a programming language, in our case, Java, and is a human-readable form to address a specific challenge. Let’s look at it from a different perspective.

We can state that writing a program is considered a goal. The goal has its reason defined by known needs or requirements in most cases. Expectations and limitations are defined. When the goal is known, each action is chosen with the aim of achieving it. The goal is evaluated, organized, and placed in the context of the destination, where the destination means a work program addressing the required challenge. Imagine all the difficulties mentioned in the previous sections.

Day after day, a new solution is posed, instead of a transparent solution. Every day, another local success keeps the project afloat, despite everything looking good on the surface.

Currently, most teams follow the SCRUM framework. Imagine a situation where the team follows the SCRUM framework (see Reference 4) and application development begins to deviate from the goal. Daily standup meetings run smoothly from time to time: it is mentioned that a fundamental error has been found. A few days later, the bug is successfully fixed with great applause. Interestingly, the frequency of such notifications is growing – more corrections, more applause. But does this really mean that the project is moving towards its goal? Does this mean that the application works? Let’s look at the answer.

There is a darker side – the backlog is growing with features and technical debt. Technical debt is not necessarily a terrible thing. Technical debt can stimulate the project and can be especially useful in the concept validation phase. The problem with technical debt occurs when it is not recognized, ignored, and poorly evaluated – even worse when technical debt starts being labeled as new features.

Although the product backlog should be one entity, it begins to consist of two different and unfortunately incompatible parts – the business and the sprint backlog (mostly technical debt). Of course, the team is working on a sprint backlog that comes from planning meetings, but with increasing technical debt, there is less and less room for the relevant business functions of the product. The trends observed in this way can result in extremely tricky situations during each new sprint planning session, where the development resources should be allocated. Let’s stop for a moment and recall this situation where the team cannot move the product forward due to technical debt.

The values of the SCRUM methodology can be simplified to courage, concentration, determination, respect, and openness. These values are not specific to the SCRUM framework. Because the team’s motivation is to deliver the product, they all sound very logical and fair.

We will now refresh our memory of the state the team has achieved. A state where it cannot move the project forward and struggles with the definition and proper consolidation of technical departments. This means that the team is doing its job, but may deviate from achieving its ultimate goal. Every discussion is extremely difficult because it is difficult to solve and describe the problem correctly for many different reasons. It may seem that developers may lose their language of communication and begin to misunderstand each other. We can see that the entropy of the software has increased because the coherence is not maintained. The project is beginning to rot and convergence to the inevitable wasted development time increases.

Let us take another deep breath and think together about how to prevent such a situation. It must be possible to identify these tendencies. Usually, each team has some commonality: the team is not always homogeneous in terms of knowledge, but this should not prevent us from identifying the degradation of the learning curve.

The project learning curve can help us identify a rotting project. Instead of gradual improvements towards the goal, the team experiences local successes full of technical repairs and solutions. Such successes do not even correspond to the values of SCRUM and gradual improvement seems unlikely. The solution may not be considered an improvement because it is specific to a particular movement and may violate the specifications of the technology used. During the solution period, the team may not acquire any useful knowledge applicable to the future. This can soon be considered a missing business opportunity due to the inability to supply business elements or only parts of them.

In addition to the degradation of the learning curve, other symptoms can be identified. This can be described as an inability to test a business function. Project code is proving sticky, dependencies are out of control, which can also harm code readability, testability, and, of course, programmer discipline. The daily goal of the software designer can be reduced to closing a ticket.

To avoid getting to this state, this book will provide some guidelines for solving the most common problems in the following chapters by introducing and questioning different types of design patterns. The design patterns are in line with the aforementioned basic pillars of OOP and APIE and promote the principles of SOLID.

What’s more, design patterns can highlight any misunderstood directions and enforce the don’t repeat yourself (DRY) principle. As a result, there is much less duplication, code testability, and more fun on the project.

That brings us to the end of this chapter.

 

Summary

Before we embark on the journey of researching design patterns, let us quickly summarize. This chapter has expanded or improved our understanding of various areas. Each of these areas affects program code from different perspectives:

  • Code transparency and readability
  • The ability to solve complex challenges
  • Following SOLID and OOP principles
  • Code testability (it’s possible to verify the purpose of the code)
  • Easy to extend and modify
  • Supporting continual refactoring
  • Code is self-explanatory

The program code is written – well done. The next chapter will take us through a survey of the implementation platform – in our case, the Java platform. We will learn in more detail how and what it means to run a program.

 

Questions

  1. What interprets the Java code to the platform and how?
  2. What does the acronym APIE represent?
  3. What types of polymorphism does the Java language allow?
  4. What principle helps software designers to produce maintainable code?
  5. What does the OCP mean?
  6. What should be considered about design patterns?
 

Further reading

  • The Garbage Collection Handbook: The Art of Automatic Memory Management, Anthony Hosking, J. Eliot B. Moss, and Richard Jones, CRC Press, ISBN-13: 978-1420082791, ISBN-10: 9781420082791, 1996.
  • Design Principles and Design Patterns, Robert C. Martin, Object Mentor, 2000.
  • Keynote address - data abstraction and hierarchy, Barbara Liskov, https://dl.acm.org/doi/10.1145/62139.62141, 1988.
  • The SCRUM framework, https://www.scrum.org/, 2022.
About the Author
  • Miroslav Wengner

    Miroslav is an engineer and architect with a passion for resilient distributed systems and product quality. He is a co-author and contributor to the Robo4J project (reactive soft real-time framework for robotics/IoT). He contributes to OpenJDK, and Java Mission Control Project, and is involved in other open-source technologies. Miroslav helps developers to create resilient and extendable solutions. He has been selected to Java Champions Program, recognized as JavaOne Rockstar and he has been Elected to Java Community Process (JCP) - Executive Committee to help guide the evolution of Java technologies.

    Browse publications by this author
Practical Design Patterns for Java Developers
Unlock this book and the full library FREE for 7 days
Start now