GoF Design Templates Using Java (Part 1)

GitHub link (article code examples and more)
https://github.com/sumithpuri/skp-code-marathon-phuket

To understand the philosophical and historical perspective of the Gang of Four design patterns, I made a short 10 minute video. (It was also my PluralSight author hearing).

I have come up with my own examples to better understand the design patterns. Try downloading the code and see if it helps you understand the patterns better. Short code snippets follow each pattern so you can get quick demos. Feel free to bookmark this article as a quick reference / cheat sheet when you want to take a quick look at each one. Without further ado, let’s move on to the Pattern Observer.

Observer model

The observer model, as the name suggests, is used in scenarios where updates need to be made at multiple points (observers) based on state changes at another location (subject). Each Observer must register with the Subject individually. The Subject must also provide methods that allow observers to withdraw. Registered observers are notified of status changes through a notification method. Usually.

The example provided is of a StockBroker application, which involves the maintenance of various types of financial information. The subject is the interface of the application that provides a model for the observed class. StockData is the concrete implementation of the subject and provides the implementation for addObserver (), removeObserver (), and notifyObservers (). In addition, it maintains a list of registered observers. Income manager, investment manager and

PortfolioHandler includes the various observers used to maintain the income, investments, and portfolio of a specific stock broker. It all depends on the constantly fluctuating values ​​of stocks. They are specifically interested in the stockSymbol, stockValue and stockUnits of each individual action. Each of the Observers implements the Observer interface. The Observer interface provides the update () method, which is implemented by each of these concrete classes.

Only the basic concept is provided in the excerpts below. You can download the sample code for the full code / app.

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();
    }
}

Use StockBroker.java to run the application. Try to add your own watcher to this app. Additionally, you can try to retrieve these values ​​from a live web service and write a custom watcher to depend on it.

Decorator pattern

The Decorator template provides an elegant way to use composition to improve functionality when the expected result has a direct dependency on the compound and composition class. Finally, a string relation (via composition) or decoration can be used to achieve the desired output at run time. In real time, when the functionality of a particular product needs to be built from a base product and various other related by-products or accessories, we can rely on the decorator.

The attached example is of a Pizza application. Here, the store’s pizzas are made with different combinations of bases and toppings. This is a classic example of using the Decorator pattern. Pizza is the abstract base class for each of the pizza bases to be implemented, and ToppingDecorator is another abstract class that inherits from Pizza for each of the toppings to be implemented. Hawaii, Italian and Mexican are the concrete implementations of Pizza, while Mushroom, Onion and Chicken are the concrete implementations of ToppingDecorator. Each of these toppings encapsulates a Pizza instance. This instance, at run time, will contain another topping or the base pizza instance. Finally, it is when the cost needs to be calculated on the whole pizza that the actual value of the decorator pattern is seen and only one call is enough to calculate the total value of the invoice.

Only the basic concept is provided in the excerpts below. You can download the sample code for the full code / app.

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());
    }
}

PizzaWorld is the main class. Try adding more decorators and basic pizza classes to see if you can really get a taste of the decorator.

Singleton model

The Singleton model defines a way to keep only one instance of a class throughout a program / application runtime and provide a uniform way to access it. There are many methods in which this model can be implemented. I’ve explained the three most common scenarios here:

Eager Singleton

The simplest Singleton (download sample code here) is the one in which the instance is created when the class is loaded and stored in a static instance variable. A static getter method is then used to get this instance if necessary. Instantiating an object before its first use may not be a recommended approach.

In the example given, MediaContract (Main Thread) works on an instance of ProductionHouse (Singleton). The singleton is instantiated when the class is loaded and kept in the private static instance variable. getInstance () in ProductionHouse helps to retrieve 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;
    } 
}

Singleton Thread-Safe (most common)

To overcome the above drawback, the recommended approach is to instantiate the object on first access and also make it thread-safe (download sample code) to prevent simultaneous instantiation of threads. The downside of this method is the decrease in performance, because the method is synchronized.

As in the previous example, the classes are MediaContract (Main Thread) and ProductionHouse (Singleton). The 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;
    } 
}

Dual control locking

The disadvantage mentioned above can be critical for a very accessible object in an application. To improve this, the scope of the synchronized block is reduced to only affect the first access. This, again, has some drawbacks.

The example remains the same, the difference being in the reduced scope of synchronization within the getInstance () method – and also that it only affects the first access and not the following accesses. You can download the sample code here.

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 the three partial examples above, you can 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();
    }

}

Order model

In scenarios where we need to create a sequence of actions (or operations) and perform them at a specified time (later), we have a candidate for using the command template. Although it looks very similar to the Observer model in implementation, the usage is different and the command (actions) is only invoked on a single receiver chosen by a summoner, rather than all observers.

We will look at an example of an auction house where there are various auction items. The base abstract class of lots is represented by AuctionItem. The abstract method to implement when implementing classes is sell (). AuctionVase, AuctionFurniture, and AuctionJewel are all concrete implementations of AuctionItem. Instances of each of these are created and defined (mapped by an itemKey) in AuctionControl, which can be thought of as a remote to showcase items in AuctionStore. Each time presentItem () is invoked on the AuctionControl class, passing an itemKey, the appropriate AuctionItem instance is selected and sell () is invoked on that instance.

Only the basic concept is provided in the excerpts below. You can download the sample code for the full code / app.

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 auctionItems = new HashMap();

    public void setAuctionItem(String itemKey, AuctionItem auctionItem) {

        auctionItems.put(itemKey, auctionItem);
    }

    public void presentItem(String itemKey) {

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

Factory model

I am led to believe that the Factory Pattern is the most widely used and implemented pattern in software projects, after the Singleton Pattern. Since Singleton is only a single class level authoring model, the scale of use of the factory model should be much higher. The Factory Pattern deals with creating objects of similar types and producing them centrally, depending on the condition or type of object requested. There are many variations of the Factory Pattern, three of which I have listed below.

Simple plant

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

In the attached example, the factory is used to instantiate a specific type of object based on the operating system. All specific systems implement the System interface, which defines common methods that the concrete class of that type must implement. SystemFactory is the factory class that provides the create () method, which takes an argument of type. The standard argument decides which concrete factory is to 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

When there can be different families of products (objects) that can be instantiated, but each family of those products must be created by a specific type of factory, we define a factory method in the basic factory class. Concrete base plant implementations then replace this method to produce concrete product types, depending on the condition.

In the example, you can 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. Likewise, another family of products, such as SonyASeries, SonyBSeries and SonyCSeries will 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) which must be replaced by the creator’s 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

The Abstract factory defines a template or interface for creating similar object types or implementations. Usually, an abstract factory will encapsulate one or more factory methods within it to actually create the product.

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

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();
    }
}

To note: Only code explaining the basic concepts of the different design patterns is included in the above snippets. You can download the code each of the links above and run them on your system for a deeper understanding. You can also choose to modify the code with your own examples to consolidate your knowledge.

I will continue this article in another article describing other design templates, including the Adapter, Facade, Iterator, and Template templates.


Source link

Abdul J. Gaspar

Leave a Reply

Your email address will not be published.