Guide: Implementation of Design Patterns in Java

Ismail Vohra
CodeX
Published in
8 min readSep 5, 2021
Guide: Implementation of Design Patterns in Java

Design patterns are a popular way of development amongst Java developers. So an important question to ask is what exactly are design patterns? Design patterns are well-defined solutions to solving a specific task or problem. Design patterns themselves are programming independently and define industry standard strategies for solving common problems. By using design patterns, you can make your code more flexible, reusable, and easier to maintain.

To become a better software developer, knowing at least some popular design patterns for coding problems is helpful. In this article, we will be discussing the different types of Design patterns, and what makes them unique, so by the end of this article you might be able to select which design pattern might be suited best for your next project.

Design Patterns

Design Patterns play a very important role in Java. They improve code readability as well as provide suitable solutions to common problems. They are very popular amongst Java Developers as the language itself is based on Design Patterns. This makes Java a very popular language in the industry.

There are 3 main design patterns in the core of Java which can be further divided into subcategories. In this article, we will look at some of the most popular Design Patterns used in Java.

Creational Design Pattern

Creational Design patterns provide a way to instantiate a single object to optimize code and increase its flexibility and reusability.

1) Singleton Pattern:

The Singleton Pattern is by far the most popular design pattern used by Java developers. In the Singleton Pattern, the user creates a single global instance of an object, so that public access is provided to this instance.

Let’s say that you own a Chocolate Factory which has computer-controlled boilers. The Job of the boiler is to use chocolate and milk as ingredients, boil them and pass them on to the next phase to make chocolate bars. The code below shows a singleton instance of a Chocolateboiler.

public class ChocolateBoiler {private boolean empty;private boolean boiled;private static ChocolateBoiler uniqueInstance;private ChocolateBoiler(){empty = true;boiled = false;}public static ChocolateBoiler getInstance(){if (uniqueInstance == null){uniqueInstance = new ChocolateBoiler();}return uniqueInstance;}public void fill() {if (isEmpty()) {empty = false;boiled = false;// fill the boiler with a milk/chocolate mixture}}public void drain() {if (!isEmpty() && isBoiled()) {// drain the boiled milk and chocolateempty = true;}}public void boil() {if (!isEmpty() && !isBoiled()) {// bring the contents to a boilboiled = true;}}public boolean isEmpty() {return empty;}public boolean isBoiled() {return boiled;}}

The code raises some flags, such as not allowing it to boil if the mixture has been boiled, draining 500 gallons of an unboiled mixture, or filling the boiler when it’s already full. This helps us get a basic understanding of the singleton pattern and its use.

2) Factory Pattern:

The factory design pattern is regularly used by Java developers. It is used when a superclass has multiple sub-classes. In Java, the factory design pattern provides an interface for creating objects in a superclass but allows the type of object created in the subclass.

Imagine having a pizza shop. You need to write some code to determine the type of pizza and then go about making it. Soon you’ll realize that the Greek Flavor isn’t selling as much so you decide to take them off the menu while adding in additional trendy pizzas.

public class SimplePizzaFactory {public Pizza createPizza(String type) {Pizza pizza = null;if (type.equals(“cheese”)) {pizza = new CheesePizza();} else if (type.equals(“pepperoni”)) {pizza = new PepperoniPizza();} else if (type.equals(“clam”)) {pizza = new ClamPizza();} else if (type.equals(“veggie”)) {pizza = new VeggiePizza();}return pizza;}}public abstract class PizzaStore {public Pizza orderPizza(String type) {Pizza pizza;pizza = createPizza(type);pizza.prepare();pizza.bake();pizza.cut();pizza.box();return pizza;}abstract Pizza createPizza(String type);}

Making a single Pizza store class won’t allow us to franchise our store or help in defining different types of pizzas. So we implement a factory pattern to help us in defining our store.

This allows us to localize all the pizza-making activities to the PizzaStore class, and yet give the franchises freedom to have their regional style, all the while allowing subclasses to decide which methods to override and extending on the functionality of the concrete class.

Structural Design Patterns

Structural Patterns provide different ways to assemble objects and classes into larger structures using inheritance and composition while keeping them efficient and flexible.

1) Adapter Pattern:

The Adapter Pattern, as the name implies, provides an interface such that objects with incompatible interfaces can collaborate. The object that joins these interfaces is called an adapter.

Let us look at an example to see how does the ADapter pattern function.

Imagine defining a Duck class that implements a duck that can fly and quack.

public interface Duck {public void quack();public void fly();}

Now a subclass of a duck can be created called the MallardDuck that simply prints out what the duck is doing:

public class MallardDuck implements Duck {public void quack() {System.out.println(“Quack”);}public void fly() {System.out.println(“I’m flying”);}}

A simple turkey class can also be implemented such that the turkey object can gobble and fly just like a duck.

public interface Turkey {public void gobble();public void fly();}

Now imagine running out of duck objects. Since a turkey object behaves just like a duck object you would like to disguise a turkey as a duck. We can’t simply use the turkey class, we will have to use an adapter to change it first.

public class TurkeyAdapter implements Duck {Turkey turkey;public TurkeyAdapter(Turkey turkey) {this.turkey = turkey;}public void quack() {turkey.gobble();}public void fly() {for(int i=0; i < 5; i++) {turkey.fly();}}}

The Adapter implements the target interface and holds an instance of the Adaptee. The TurkeyAdapter class changes the turkey object such that it behaves and functions just like a duck.

2) Decorator Pattern:

The Decorator Pattern as the name suggests manipulating the output of objects by placing these objects inside special wrapper objects that contain the behaviors. This only changes the object in question while all other instances of the object remain unchanged.

Imagine running a coffee shop. Initially, their business model looked something like this:

Because you have grown so quickly you want to update your system to increase your, is beverage offerings.

If for some reason you went with the same design pattern you would have to manage multiple subclasses. Each subclass with being a permutation of the different blends, their flavors, and their condiments which can get large very fast.

Our goal is to allow classes to be easily extended to incorporate new behavior without modifying existing code. This can be done by implementing Designs that are resilient to change and flexible enough to take on new functionality to meet changing requirements. Instead, we’ll start with a beverage and “decorate” it with the condiments at runtime.

We will first start by defining the Beverage class:

public abstract class Beverage {String description = “Unknown Beverage”;public String getDescription() {return description;}public abstract double cost();}

The Beverage class is simple enough. Let’s implement the abstract class for the Condiments (Decorator) as well:

public abstract class CondimentDecorator extends Beverage {public abstract String getDescription();}

Now that we’ve got our base classes out of the way, let’s implement some beverages. We’ll start with Espresso. Remember, we need to set a description for the specific beverage and also implement the cost() method.

public class Espresso extends Beverage {public Espresso() {description = “Espresso”;}public double cost() {return 1.99;}}public class HouseBlend extends Beverage {public HouseBlend() {description = “House Blend Coffee”;}public double cost() {return .89;}}public class Mocha extends CondimentDecorator {Beverage beverage;public Mocha(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + “, Mocha”;}public double cost() {return .20 + beverage.cost();}}

We can marvel at the flexibility of the design that allows us to dynamically calculate the cost and if required extend its functionality effortlessly.

Behavioral Design Patterns

Behavioral Patterns are concerned with algorithms to provide better solutions for better interaction between objects and assignment of responsibilities between them.

1) Template Method Pattern:

The Template Method, perhaps the most important behavioral design pattern, defines a skeleton of an algorithm in the superclass but lets the subclass override specific steps of the algorithm without changing the entire structure. Let’s look at an example.

Many people start their day with a cup of coffee or tea. They both have common ingredient caffeine that gives people the boost they need for the day. Now if we were to write code for making coffee or tea we would have the same methods. For example, coffee is prepared in 4 steps, boiling the water, brewing the coffee, pouring it into a cup, and then adding milk or sugar. Tea is prepared in the same way except in the second step where we use a teabag.

We can see that both beverages have common steps that will cause repetition in code. This is where we use a template pattern to break up our algorithm into steps that we can override in subclasses. We can thus define an abstract parent class CaffineBeverages that can be extended in the Tea and Coffee subclasses. This can help us reduce repetition and cater the algorithm to our specific needs.

public abstract class CaffeineBeverage {final void prepareRecipe() {boilWater();brew();pourInCup();addCondiments();}abstract void brew();abstract void addCondiments();void boilWater() {System.out.println(“Boiling water”);}void pourInCup() {System.out.println(“Pouring into cup”);}}public class Tea extends CaffeineBeverage {public void brew() {System.out.println(“Steeping the tea”);}public void addCondiments() {System.out.println(“Adding Lemon”);}}public class Coffee extends CaffeineBeverage {public void brew() {System.out.println(“Dripping Coffee through filter”);}public void addCondiments() {System.out.println(“Adding Sugar and Milk”);}}

Final Thoughts

Design Patterns are a powerful arsenal in the pockets of a successful Java developer. They provide the blueprints that can help you customize to solve a recurring problem in your code. Design Patterns allow you to implement a solution that suits your own specific needs. They increase readability and helps other developers collaborate.

I hope this article helped you grasp the basics of design patterns and help you choose a recipe for your next programming project.

References

  • Freeman, Eric, Elisabeth Robson, Kathy Sierra, and Bert Bates. Head First Design Patterns. Sebastopol, CA: O’Reilly, 2004. Print.

--

--

Ismail Vohra
CodeX
Writer for

Social Entrepreneur & Computer Scientist, aspiring for the global common good.