Despite declarative programming styles on the rise, object-oriented programming (OOP) is clearly here to stay. After all, it still provides the most natural way of building user interfaces and lends itself to organized, systemized applications. But an understanding of OOP isn’t complete without sound knowledge of the design patterns that organize object-oriented programs.
In this article, we’ll explore the origins of these design patterns and cover the benefits of using them. We’ll also dive into three specific design patterns and how you can implement them when programming with Java.
What Are Design Patterns?
Design patterns represent best practice solutions to common software design problems. Over the last three decades of the 20th century, object-oriented software developers began creating and refining these patterns in attempts to solve general problems. After much trial-and-error, their solutions evolved into the elegant design patterns we know today.
In 1994, a group of four experienced engineers known as the “Gang of Four” published “Design Patterns: Elements of Reusable Object-Oriented Software,” a now-classic book in the software engineering field that covers 23 still-pertinent patterns. These design patterns are so important to OOP that the Java Development Kit (the Java compiler) uses them internally. You can check the source code at GitHub.
In offering solutions to engineering issues, design patterns uphold the four major building blocks of OOP: encapsulation, abstraction, inheritance and polymorphism. Patterns also maintain SOLID design principles, making for all-around good code.
However, developers should carefully select the right pattern depending on their project. Even selecting an appropriate pattern when it’s unneeded can be the difference between overly complex code and code that’s clean and readable.
The Gang of Four broke design patterns down into three categories. Creational design patterns focus on different ways to create objects, giving programs more flexibility in deciding which objects need to be created and when. Structural design patterns concern the relationships between objects and often utilize the concept of inheritance to obtain new object functionalities. Behavioral design patterns are specifically concerned with interaction or communication between objects.
Now that we have a basic grasp of design patterns, let’s take a look at how programmers can benefit from them.
Benefits of Design Patterns
Design patterns provide a plethora of benefits to programmers. First, they help us communicate with other developers at an abstract level. For example, you can tell your colleagues that you’ll be using a singleton pattern to improve your code, and they’ll know what you mean. With this standard terminology, you don’t have to write out a lot of code to express your ideas.
Second, implementing these patterns makes you a better designer. Learning them helps you become skilled at writing reusable, extensible and maintainable software. The more you practice, the more you’ll find yourself recognizing which design pattern to implement when creating a new coding project.
On a related note, learning design patterns helps you master new frameworks. When familiarizing yourself with a framework or system, recognizing patterns enables you to respond to it in a much more efficient way than if you were trying to figure it out from scratch. If you’re a novice developer, patterns will assist you in learning software design in a fast and organized way.
Since Java is an object-oriented language, all Java developers should know how to use design principles. In fact, programmers use design principles at all stages of software design, but especially during the first three phases of SDL — these being the (i) analysis and requirement, (ii) application architecture design and (iii) implementation phases. With that said, let’s look at some specific patterns and see how an experienced developer might implement them.
Examples of Design Patterns
The Gang of Four covered 23 classic design patterns in their influential book. Here, we’ll take a look at 3 of those patterns, taking one from each category (behavioral, creational and structural). We’ll gain insight into how these patterns work in real-world situations and why using them in specific circumstances will make your programming life easier.
This behavioral design pattern turns an order into a standalone object that stores the order’s information for subsequent execution. If you’re at a restaurant and place an order, your waiter’s notepad would be analogous to this command object. An object called an invoker transfers the command to another object called the receiver to execute a specific action. You could think of your waiter as the invoker and your chef as the receiver. Let’s now look at a more tech-related example to better understand this pattern.
Say you’re creating a new remote control for your TV and need to figure out how to design it. You start by configuring the “on” button and decide to use the command pattern for your system. Good choice! It follows that your invoker will be the button itself, and a press() method inside the invoker will spur the system to act. The invoker deviceButton then calls a method called execute() on the command object.
So, what exactly is the command object? In this case, it’s an object we could call TurnTVOn , whose job is to turn the TV on. With its execute() method, TurnTVOn tells the receiver, the TV object, to execute a specific method. In this case, the specific receiver method is on(), but the receiver usually holds all kinds of methods, just as a chef has many recipes. The point is that it’s the command object that encapsulates specific information about the request and is able to pass it to the appropriate method of receiver.
How’s the command pattern useful to you as the developer? First, it allows you to store a list of requests that you want to delay or queue. On top of that, you can set aside multiple commands in a class to use repeatedly, such as turning the TV on or off. This pattern also supports the implementation of undoable operations for past commands. Finally, by following this pattern when working with request-response models, you uphold loose coupling, a design principle that leads to maintainable code.
Factory Method Pattern
The factory method, a creational design pattern, is one of the most commonly-used patterns in Java. When utilizing this pattern, developers make a factory class, which serves to dynamically return one of the multiple specific types of objects based on user input.
These specific classes must share an interface, which you could think of as a contract that specifies a class’s capabilities. This interface supports different types of specific objects because they implement the generic object’s interface. The factory takes user input and then uses that interface to determine the specific type of object to spin up. This might sound confusing, so let’s look at an example.
Imagine you have different types of servers — say, FTP servers and mail servers — that you want to spin up at different times. To utilize the factory method, you’d first create a generic server interface and extend that interface when designing classes for FTP and mail servers. You’d then create your server factory, which would dynamically return a generic server object, which is then able to create and support the specific types of servers. For another example, we recommend watching this YouTube video by a design pattern expert.
This pattern is a great choice when you want to centralize class selection code, and remove conditionals determining specific class type from the main portion of your app. Using an interface to create objects extracts the complicated logic from the client program and encapsulates it into factory methods.
Because this pattern takes the responsibility for object instantiation off the client, we end up with a program that is loosely-coupled, more robust and easier to extend. For example, we can change the FTP class implementation or add a new Server class without the client portion of the app becoming aware of the changes.
The adapter pattern falls in the structural bucket — that is, it’s concerned with the relationships between objects. More specifically, this design pattern allows objects with incompatible interfaces to work together.
We can use the classic conflict between square pegs and round holes to illustrate this pattern. Say your bathroom door locks with a sliding bolt that’s attached to the door and needs to fit into a hole in the door frame. But, the bolt is square while the hole is round. You really like to shower in peace, so to solve the problem you’d create an adapter: One end accommodates the square bolt, while the other slides right into the door frame. You’ve just used the adapter design pattern!
In the programming world, you’d want to use the pattern when you have a third-party library, legacy class or other existing class with an interface that’s incompatible with the rest of your code. Rather than refactor your entire codebase to accommodate the existing class, you can create an adapter class to serve as a middle layer between the two interfaces.
The adapter pattern can also simplify your code base use case by adding missing functionality to objects. In some circumstances, you may want to add some features to subclasses that you can’t add to the superclass. Instead of extending the subclasses and copy-pasting the same functionality into all the subclasses’ child classes, you can create a single adapter class that incorporates this missing functionality. You could then wrap the adapter around the objects with missing features.
Continue Your Java Journey
We hope you now have a solid understanding of Java design patterns. But before you can write beautiful and maintainable code, you’ll need to fully master not only design patterns but a variety of other Java fundamentals.
Demand for Java developers is at an all-time high, so don’t delay the process. Sharpen your skills with our world-class Nanodegree to Become a Java Web Developer.