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
You can find the code files for this chapter on GitHub at https://github.com/PacktPublishing/Practical-Design-Patterns-for-Java-Developers/tree/main/Chapter01.
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)
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
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
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..."); } }
Example 1.1 – The Vehicle class hides an internal state (moving)
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
- Changing the number of method 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
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...
Example 1.2 – The Vehicle variable holds the reference to the Car instance and the appropriate move method is executed at runtime (see Figure 1.6)
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:

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..."); } }
Example 1.3 – The extraction of the common functionality without providing a particular implementation by using an abstract class concept

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..."); } }
Example 1.4 – A similar functionality extraction by using Java interfaces
Both concepts of abstraction can be combined (see Figure 1.9):

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
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
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
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!
Example 1.5 – Although both are considered entities, Truck and Car inherit a Vehicle interface, the move method is compliant, and this causes an issue in extension or execution
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!
Example 1.6 – The Vehicle interface provides a move abstraction method
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());
Example 1.7 – A CarWash example where any Vehicle type can be substituted by appropriate instances of classes in the class hierarchy
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());} }
Example 1.8 – Various implementations of inherited method abstraction
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()); }
Example 1.9 – The functionality split into smaller units (interfaces) based on the purpose
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); } }
Example 1.10 – The garage implementation depends on vehicle abstraction, not concrete classes in a hierarchy
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
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.