JavaIntroduction to Software Design Patterns Using Java

Introduction to Software Design Patterns Using Java

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

I was going through the book “Head First Design Patterns” by Elisabeth Freeman and Kathy Sierra and came up with my own examples to understand their software design concepts better. You’ll find several design examples below, along with sample Java code implementing the designs. This also builds on the earlier work of the “Gang of Four” authors of “Design Patterns: Elements of Reusable Object-Oriented Software,” the classic work on object-oriented software design patterns. This is the first of a four-part series.

Singleton Pattern – Creational Design Pattern

The Singleton Pattern defines a way to maintain only a single instance of a class in the entire execution of a program/application and to provide a uniform way to access it. There are numerous methods in which this pattern can be implemented. I have explained the three most common scenarios here.

Eager Singleton

The simplest singleton is the one in which the instance is created at class-load time, and stored in a static instance variable. A static getter method is then used to get this instance when required. The instantiation of an object earlier than its first use might not be a recommended approach.

In the given example, MediaContract (Main Thread) works on an instance of the ProductionHouse (Singleton). The Singleton is instantiated at class-load time and maintained in the private static instance variable. getInstance() in ProductionHouse helps in retrieving the instance.


package com.sumsoft.design.patterns.singleton.eager;

/*
 * @author Sumith Puri
 */
public class ProductionHouse {

    private static ProductionHouse productionHouse = new ProductionHouse();

    private ProductionHouse() {

    }

    public static synchronized ProductionHouse getInstance() {

        return productionHouse;
    } 
}


Thread-Safe Singleton (Most Common)

To overcome the above drawback, the recommended approach is to instantiate the object at the first access time and also to make it thread-safe to prevent concurrent thread instantiation. The disadvantage of this method is poorer performance, as the method is synchronized.

As in the earlier example, the classes are MediaContract (Main Thread) and ProductionHouse (Singleton). getInstance() method is synchronized and the instance is created only if it is null.


package com.sumsoft.design.patterns.singleton.threadsafe;

/*
 * @author Sumith Puri
 */
public class ProductionHouse {

    private static ProductionHouse productionHouse = null;

    private ProductionHouse() {

    }

    public static synchronized ProductionHouse getInstance() {

        if(productionHouse == null) {
            productionHouse = new ProductionHouse();
        }

        return productionHouse;
    } 
}


 

Double-checked Locking

The disadvantage mentioned above can be critical for a highly accessed object in an application. To better this, the scope of the synchronized block is reduced to affect only the first access. This again has some disadvantages. I recommend reading on Initialization on Demand Holder Idiom.

The example remains the same, the difference being in the reduced scope of synchronisation within the getInstance() method and also that it affects only the first access and not subsequent accesses.



package com.sumsoft.design.patterns.singleton.doublechecked;

/*
 * @author Sumith Puri
 */
public class ProductionHouse {

    private static ProductionHouse productionHouse = null;

    private ProductionHouse() {

    }

    public static ProductionHouse getInstance() {

        if(productionHouse == null) {
            synchronized(ProductionHouse.class) {
                if(productionHouse == null) {
                    productionHouse = new ProductionHouse();
                }
            }
        }

        return productionHouse;
    } 
}


 

For all three partial samples above, you may use the following code to run and understand the different ways to instantiate Singletons.



package com.sumsoft.design.patterns.singleton.doublechecked;

/*
 * @author Sumith Puri
 */
public class MediaContract extends Thread {

    public void run() {
        getProductionHouse();
    }

    public void getProductionHouse() {
        ProductionHouse productionHouse = ProductionHouse.getInstance();
        System.out.println(productionHouse.toString());
    }

    public static void main(String args[]) {

        MediaContract thread01 = new MediaContract();
        thread01.start();

        MediaContract thread02 = new MediaContract();
        thread02.start();
    }

}

Factory Pattern – Creational Design Pattern

Factory Pattern, I believe, is the most widely used and implemented pattern in software projects after the Singleton Pattern. Since Singleton is only a creational pattern at the single-class level, the scale of the effect of usage of Factory should be much higher. The Factory Pattern deals with the creation of similar types of objects and production in a centralized manner, depending on the condition or type of object requested. There are variations of the usage of factory pattern, three of which I have listed below:

Simple Factory

The simplest factory is the one that is used to create (instantiate) a specific type of product (object) depending on a condition. The specific types of objects that can be created in a single factory are all expected to implement a single interface.

In this example, the factory is used to instantiate a specific type of object depending on the operating system. All the specific systems implement the System interface, which defines the common methods that the concrete class of this type should implement. SystemFactory is the factory class that provides the create() method, which takes a type argument. The type argument decides which concrete factory should be instantiated.


package com.sumsoft.design.patterns.factory.simple;

/*
 * @author Sumith Puri
 */
public interface System {

    public void provision();
    public void update();
    public void restart();

}


 

package com.sumsoft.design.patterns.factory.simple;

/*
 * @author Sumith Puri
 */
public class UnixSystem implements System {

    @Override
    public void provision() {
        // TODO Auto-generated method stub

    }

    @Override
    public void restart() {
        // TODO Auto-generated method stub

    }

    @Override
    public void update() {
        // TODO Auto-generated method stub

    }
}


 

package com.sumsoft.design.patterns.factory.simple;

/*
 * @author Sumith Puri
 */
public class SystemFactory {

    public System createSystem(String type) {
        System system = null;
        if(type.equals("WIN")) {
            system = new WindowsSystem();
        } else if (type.equals("LIN")) {
            system = new LinuxSystem();
        } else if (type.equals("SOL")) {
            system = new SolarisSystem();
        } else if (type.equals("MAC")) {
            system = new MacosSystem();
        } else {
            system = new UnixSystem();
        }

        return system;
    }

}


Factory Method

Organizations must go beyond a piecemeal approach to networking and security. A broad, integrated, and automated platform that secures all edges addresses challenges now and in the future.

When there can be various families of products (objects) that can be instantiated, but each family of these products needs to be created by a specific type of factory, we define a factory method in the base factory class. The concrete implementations of the base factory then override this method to produce a concrete type of products, depending on the condition.

In the example below, you’ll notice the presence of two abstract classes, Mobile (Product) and MobileStore (Creator). One family of concrete product implementations are NokiaASeries, NokiaBSeries and NokiaCSeries, to be created by the NokiaStore, which is the concrete implementation of the creator. In similar fashion another family of products, such as SonyASeries, SonyBSeries and SonyCSeries, are to be created by SonyStore, another concrete implementation of MobileStore. MobileStoreCentre is the main class to run this application. The createMobile() method is the abstract method (factory method) that is to be overridden by the creator implementations.



package com.sumsoft.design.patterns.factory.method;

/*
 * @author Sumith Puri
 */
public abstract class Mobile {

    public void chassis() {
        System.out.println("Default Chassis Included.");
    }

    public void experience() {
        System.out.println("Default Experience Hardware.");
    }

    public void integrity() {
        System.out.println("Default Integrity Check.");
    }

    public void box() {
        System.out.println("Default Box Packaging.");
    }

    public void software() {
        System.out.println("Default Software Bundled.");
    }
}


 

package com.sumsoft.design.patterns.factory.method;

/*
 * @author Sumith Puri
 */
public class NokiaASeries extends Mobile {

    public void experience() {
        System.out.println("Nokia Premium Hardware.");
    }
}


 

package com.sumsoft.design.patterns.factory.method;

/*
 * @author Sumith Puri
 */
public class SonyASeries extends Mobile {

    public void experience() {
        System.out.println("Sony Premium Hardware.");
    }
}


 

package com.sumsoft.design.patterns.factory.method;

/*
 * @author Sumith Puri
 */
public abstract class MobileStore {


    public Mobile assemble(String make) {
        Mobile mobile;
        mobile = createMobile(make);
        mobile.chassis();
        mobile.integrity();
        mobile.experience();
        mobile.software();
        mobile.box();

        return mobile;
    }

    protected abstract Mobile createMobile(String make);

}


 

package com.sumsoft.design.patterns.factory.method;

/*
 * @author Sumith Puri
 */
public class NokiaStore extends MobileStore {

    @Override
    protected Mobile createMobile(String make) {
        Mobile mobile = null;

        if(make.equals("ASeries")) {
            mobile = new NokiaASeries();
        } else if(make.equals("BSeries")) {
            mobile = new NokiaBSeries();
        } else if(make.equals("CSeries")) {
            mobile = new NokiaCSeries();
        }

        return mobile;
    }

}


 

package com.sumsoft.design.patterns.factory.method;

/*
 * @author Sumith Puri
 */
public class MobileStoreCentre {

    public static void main(String args[]) {

        MobileStore mobileStore01 = new NokiaStore();
        MobileStore mobileStore02 = new SonyStore();

        Mobile mobile01 = mobileStore01.assemble("ASeries");
        Mobile mobile02 = mobileStore02.assemble("BSeries");
    }
}


Abstract Factory

AbstractFactory defines a template or interface for creation of similar types of objects or implementations. Usually, AbstractFactory will encapsulate a factory method or more within for actual creation of the product.

Taking the same example as above, MobileStoreFactory instantiates the concrete instance of the abstract factory (MobileStore) based upon the variable specified, either “Nokia” (NokiaStore) or “Sony” (SonyStore). The factory is then responsible for creating the objects of similar types based upon the choice – such as “ASeries” or “BSeries” or “CSeries”. The mobile is then assembled based upon this by the MobileStore. You may use MobileStoreCentre to run this example and understand the design pattern based on the output.


package com.sumsoft.design.patterns.factory.abstract_;

/**
 * @author sumith_puri
 *
 * The Abstract Factory Design Pattern will instantiate the appropriate abstract
 * factory.
 */
public class MobileStoreFactory {

    // Get Abstract Factory
    public static MobileStore getMobileStore(String mobileStore) {

        MobileStore mStore = null;
        if ("Nokia".equalsIgnoreCase(mobileStore))
        mStore = new NokiaStore();
        else if ("Sony".equalsIgnoreCase(mobileStore))
        mStore = new SonyStore();
        return mStore;
    }
}


 

package com.sumsoft.design.patterns.factory.abstract_;

/*
 * @author Sumith Puri
 */
public class MobileStoreCentre {

    public static void main(String args[]) {

        MobileStore mobileStore = MobileStoreFactory.getMobileStore("Nokia");
        Mobile mobile = mobileStore.assemble("ASeries");
        mobile.experience();

        System.out.println("");

        mobileStore = MobileStoreFactory.getMobileStore("Sony");
        mobile = mobileStore.assemble("BSeries");
        mobile.experience();
    }
}


 

Observer Pattern – Behavioral Design Pattern

Observer Pattern, as the name suggests, is used in scenarios when updates need to be done at multiple points (Observers) depending on changes in state at another place (Subject). Each of the Observers has to register themselves with the Subject, individually. The Subject should also provide a method that allows the Observers to remove themselves. Registered Observers are informed of changes in state through a notify method, usually.

The provided example is that of a StockBroker application, which involves maintenance of various types of financial information. Subject is the interface in the application, which provides a template for the Observed class. StockData is the concrete implementation of Subject and provides implementation of addObserver(), removeObserver() and notifyObservers(). Additionally, it maintains a list of registered observers. IncomeHandler, InvestmentHandler and PortfolioHandler are the various observers used to maintain income, investment and portfolio of a specific StockBroker. All these depend on the constantly fluctuating values of stocks. They are specifically interested in the stockSymbol, stockValue and stockUnits of each individual stock. Each of the observers implements the interface Observer. The Observer interface provides the update() method, which is implemented by each of these concrete classes.

Use StockBroker.java to run the application. Try adding your own observer to this application. Also, you can try picking up these values from a live web service and then write a custom observer that depends on this.


package com.sumsoft.design.patterns.observer;

/*
 * @author Sumith Puri
 */
public interface Observer {

    public void update(String stockSymbol, Float stockValue, Integer stockUnits);

}


 

package com.sumsoft.design.patterns.observer;

/*
 * @author Sumith Puri
 */
public class IncomeHandler implements Observer {

    Subject stockData = null;

    public IncomeHandler(Subject stockData) {
        this.stockData = stockData;
        stockData.addObserver(this);
    }

    @Override
    public void update(String stockSymbol, Float stockValue, Integer stockUnits) {
        System.out.println("IncomeHandler received changes... ");
    }

}


 

package com.sumsoft.design.patterns.observer;

/*
 * @author Sumith Puri
 */
public interface Subject {

    public void addObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}


 

package com.sumsoft.design.patterns.observer;

import java.util.ArrayList;
import java.util.List;

/*
 * @author Sumith Puri
 */
public class StockData implements Subject {

    private String stockSymbol = null;
    private Float stockValue = null;
    private Integer stockUnits = null;
    private List observers = null;

    public StockData() {
        observers = new ArrayList();
    }

    @Override
    public void addObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void notifyObservers() {
        for(Observer o: observers) {
            o.update(stockSymbol, stockValue, stockUnits);
        }
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    public void setStockData(String stockSymbol, Float stockValue, Integer stockUnits) {
        // In real-time, this method might be invoked with values from a live web service at regular intervals.
        this.stockSymbol = stockSymbol;
        this.stockValue = stockValue;
        this.stockUnits = stockUnits;
        setDataChanged();
    }

    private void setDataChanged() {
        notifyObservers();
    }
}

Decorator Pattern – Structural Design Pattern

The Decorator Pattern provides an elegant way to use composition for enhancing functionality, where the result expected has direct dependency on the composed and composing class. A chain relation (via composition) or decoration can be finally used to achieve the desired output at runtime. In real-time, when the functionality of one particular product is expected to be built from a base product and various other related sub-products or fixtures, we can rely on the Decorator.

This example is that of a Pizza application. Here, the pizzas in the shop are made with various combinations of bases and topping combinations. This is a classical example for usage of the decorator pattern. Pizza is the abstract base class for each of the pizza bases to implement and ToppingDecorator is another abstract class that inherits from Pizza for each of the toppings to implement. Hawaiian, Italian and Mexican are the concrete implementations of Pizza, whereas Mushroom, Onion and Chicken are the concrete implementations of ToppingDecorator. Each of these toppings encapsulates a Pizza instance. This instance, at runtime, will hold another topping or the pizza base instance. Finally, it is when the cost has to be calculated on the entire pizza that the real value of the decorator pattern is seen and just one call suffices to calculate the entire bill value.

PizzaWorld is the main class. Try adding more decorators and pizza base classes to see if you can get a real taste of the Decorator!

 
package com.sumsoft.design.patterns.decorator;

/*
 * @author Sumith Puri
 */
public abstract class ToppingDecorator extends Pizza {

    public abstract String getDescription();
}


 

package com.sumsoft.design.patterns.decorator;

/*
 * @author Sumith Puri
 */
public class Mushroom extends ToppingDecorator {

    Pizza pizza;

    public Mushroom(Pizza pizza) {

        this.pizza = pizza;
    }

    @Override
    public String getDescription() {

        return pizza.getDescription() + ", Mushroom";
    }

    @Override
    public double cost() {

        return 0.25 + pizza.cost();
    }

}


 

package com.sumsoft.design.patterns.decorator;

/*
 * @author Sumith Puri
 */
public abstract class Pizza {

    protected String description = null;

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}


 

package com.sumsoft.design.patterns.decorator;

/*
 * @author Sumith Puri
 */
public class Italian extends Pizza {

    public Italian(String description) {
        this.description = description  + ", Italian";
    }

    @Override
    public double cost() {

        return 1.20;
    }

}


 

package com.sumsoft.design.patterns.decorator;

/*
 * @author Sumith Puri
 */
public class PizzaWorld {

    public static void main(String args[]) {

        Pizza pizza = new Hawaiian("Veg Exotica");
        pizza = new Mushroom(pizza);
        pizza = new Mushroom(pizza);
        pizza = new Onion(pizza);

        Pizza pizza1 = new Italian("Non-Veg Supreme");
        pizza1 = new Chicken(pizza1);
        pizza1 = new Chicken(pizza1);
        pizza1 = new Onion(pizza1);
        pizza1 = new Onion(pizza1);

        System.out.println("Pizza World");
        System.out.println("===========");
        System.out.println("");
        System.out.println(pizza.getDescription() + " " + pizza.cost());
        System.out.println(pizza1.getDescription() + " " + pizza1.cost());
    }
}


 

Command Pattern – Behavioral Design Pattern

In scenarios where we need to create a sequence of actions (or operations) and perform them at a specified (later) point in time, we have a candidate for usage of the Command Pattern. Though it very closely resembles the Observer pattern in implementation, the usage is different and the command (actions) is invoked only on a single chosen receiver by an invoker, rather than on all observers.

The example is of an Auction House where there are various items for auction, the base abstract class of which is represented by AuctionItem. The abstract method to be implemented by implementing classes is sell(). AuctionVase, AuctionFurniture and AuctionJewel are all concrete implementations of AuctionItem. Instances of each of these are created and set (mapped by an itemKey) into the AuctionControl, which can be thought of as a remote control for presenting items in the AuctionStore. Whenever the presentItem() is invoked on the AuctionControl class, passing in an itemKey, the appropriate AuctionItem instance is selected and sell() is invoked on this instance.

 

package com.sumsoft.design.patterns.command;

/*
 * @author Sumith Puri
 */
public abstract class AuctionItem {

    public void sell() {

    }
}


 

package com.sumsoft.design.patterns.command;

/*
 * @author Sumith Puri
 */
public class AuctionFurniture extends AuctionItem {

    public void sell() {
        System.out.println("Sold Furniture Item");
    }
}


 

package com.sumsoft.design.patterns.command;

import java.util.HashMap;
import java.util.Map;

/*
 * @author Sumith Puri
 */
public class AuctionControl {

    Map<String, AuctionItem> auctionItems = new HashMap<String, AuctionItem>();

    public void setAuctionItem(String itemKey, AuctionItem auctionItem) {

        auctionItems.put(itemKey, auctionItem);
    }

    public void presentItem(String itemKey) {

        AuctionItem auctionItem = auctionItems.get(itemKey);
        auctionItem.sell();
    }
}


 

GitHub Repository for Code Samples

Here is the GitHub repository for the code samples above, and more: https://github.com/sumithpuri/skp-code-marathon-phuket.

This is the first of four parts, so keep an eye out for the rest, and happy Design Patterns with Core Java!

A final exercise:

Find out where exactly (Examples) within the Core Java API or Feature Implementation are the above Design Patterns being used?

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories