JavaSoftware Design Patterns Using Java: Adapters and More

Software Design Patterns Using Java: Adapters and More

We looked at a number of software design patterns in the first part of this series. Here we’ll continue our Java implementation of the design principles outlined by Elisabeth Freeman, Kathy Sierra and the “Gang of Four” authors.

Adapter Pattern

What do you do if you need to use a hairdryer in a different part of the world, each with different socket types? I would seek an adapter! As in real life, when we want to plug and play with similar but incompatible interfaces, we use an Adapter pattern. The Adapter adapts the Adaptee to the desired interface by composing the Adaptee object and inheriting the desired interface or by multiple inheritance.

Here’s a real-world computer scenario, where I want to plug in an external hard drive (pre-USB era!), SeagateDrive of interface type SeagateGeneric, to an incompatible computer, SamsungComputer of type Computer. SeagateGeneric provides read() and write() methods for the specified purposes, which need to be adapted to the actual bufferData(), flushData() and purgeData() methods of the Computer. Note that there is no equivalent of purgeData().

The ideal way to handle this scenario is to throw an exception whenever this method is invoked on the hard drive as it would do in the real world. The adapter to perform the translation in this scenario is the SeagateAdapter, which implements the Computer interface. It encapsulates a SeagateGeneric instance reference, and adapts it to the Computer interface. Whenever a bufferData() method is invoked on the Computer interface, it actually requires three invocations of read() on the SeagateGeneric implementation to match up to the Computer’s standards. These kinds of translations are done by the adapter.

PCAssembler is the main class here. Try adding your own device and its adapter to the computer.

 
package com.sumsoft.design.patterns.adapter;
 
/*
 * @author Sumith Puri
 */
public interface Computer {
 
    public void flushData();
 
    public void bufferData();
 
    public void purgeData();
}
 
 
package com.sumsoft.design.patterns.adapter;
 
/*
 * @author Sumith Puri
 */
public interface SeagateGeneric {
 
    public void read();
 
    public void write();
}
 
 
package com.sumsoft.design.patterns.adapter;
 
/*
 * @author Sumith Puri
 */
public class SeagateDrive implements SeagateGeneric {
 
    @Override
    public void read() {
        System.out.println("Reading @ 7200 RPM from Seagate B Series");
 
    }
 
    @Override
    public void write() {
        System.out.println("Writing @ 1 Mbps to Seagate B Series");
 
    }
 
}
 
 
package com.sumsoft.design.patterns.adapter;
 
/*
 * @author Sumith Puri
 */
public class SeagateAdapter implements Computer {
 
    SeagateGeneric seagateGenericDrive;
 
    public SeagateAdapter(SeagateGeneric seagateGenericDrive) {
        this.seagateGenericDrive = seagateGenericDrive;
    }
 
    @Override
    public void bufferData() {
        seagateGenericDrive.read();
        seagateGenericDrive.read();
        seagateGenericDrive.read();
 
    }
 
    @Override
    public void flushData() {
        seagateGenericDrive.write();
        seagateGenericDrive.write();
 
    }
 
    @Override
    public void purgeData() {
        System.out.println("Operation Not Supported: Seagate Drive cannot Purge Data...");
 
    }
}

Facade Pattern

Consider a scenario where we require multiple method invocations on various classes to achieve the desired functionality. Also, consider that this set of functionality is repeatedly being used in your code. If you are thinking of an option where you will perform direct invocations, you are bound to end up with code maintenance issues and tightly coupled code. If these invocations are remote, it is going to be worse for performance. This is where the facade comes into play, wherein multiple method invocations are encapsulated into a single method of the facade class to achieve the desired functionality. It provides us with a single point of change and looser coupling for the individual implementations. Remote method invocation patterns like SessionFacade (EJB) adapt from here to improve overall performance and lower complexity.

An example is a very simple scenario of an InvoiceManagerFacade that has addInvoice() and deleteInvoice() methods. To achieve the desired result, each of these methods encapsulates the method invocations from OrderManager, LedgerManager and BillingManager classes.

AccountsCentral is the main class. Try adding your own method to the facade class, or try plugging in a new type of facade.


package com.sumsoft.design.patterns.facade;
 
/*
 * @author Sumith Puri
 */
public interface InvoiceSessionFacade {
 
    public void addInvoice(Invoice invoice);
 
    public void deleteInvoice(Invoice invoice);
}
 
 
package com.sumsoft.design.patterns.facade;
 
/*
 * @author Sumith Puri
 */
public class InvoiceSessionFacadeImpl implements InvoiceSessionFacade {
 
    // This is only for the example, Do not follow 
    // this kind of initialisation in your code
 
    OrderManager orderManager = new OrderManager();
    LedgerManager ledgerManager = new LedgerManager();
    BillingManager billingManager = new BillingManager();
 
 
    @Override
    public void addInvoice(Invoice invoice) {
 
        orderManager.initOnInvoice(invoice.getInvoiceId());
        ledgerManager.initOnInvoice(invoice.getInvoiceId());
        billingManager.initOnInvoice(invoice.getInvoiceId());
    }
 
    @Override
    public void deleteInvoice(Invoice invoice) {
 
        orderManager.purgeInvoice(invoice.getInvoiceId());
        ledgerManager.cascadeDeleteInvoice(invoice.getInvoiceId());
        billingManager.deleteInvoice(invoice.getInvoiceId());
    }
 
}

Template Pattern

Imagine a real-world scenario where a factory is creating both aluminium nails and screws. Though the machine must create both of them through similar processes, the way some steps are implemented may vary in each of these. When we think of such scenarios in software, we utilize the template pattern. The template pattern defines a way to reuse algorithms for various implementations with different or slightly different outcomes.

In this example, the abstract class SoftwareProcessor defines a general set of algorithmic steps (functions) to deliverSoftware(). This class is my template class. Since the implementation and testing phases differ in projects based on the technology stack being used, CProcessor and JavaProcessor classes adapt this algorithm for these phases. The common methods are all implemented in SoftwareProcessor and the specific ones are left as abstract.

SoftwareConsultants can be used to run this example. Try adding your own processor.


 
package com.sumsoft.design.patterns.template;
 
/*
 * @author Sumith Puri
 */
abstract class SoftwareProcessor {
 
    public void deliverSoftware() {
 
        requirementsClarification();
        functionalSpecification();
        technicalSpecification();
        implementModules();
        testModules();
 
        if (!isPlatformIndependent())
            platformTest();
 
        supportPhase();
    }
 
    public void requirementsClarification() {
        System.out.println("Default Requirements Clarification");
    }
 
    public void functionalSpecification() {
        System.out.println("Default Functional Specification");
    }
 
    public void technicalSpecification() {
        System.out.println("Default Technical Specification");
    }
 
    public abstract void implementModules();
 
    public abstract void testModules();
 
    public boolean isPlatformIndependent() {
        return false;
    }
 
    public abstract void platformTest();
 
    public void supportPhase() {
        System.out.println("Default Support Contract");
    }
}
 
 
package com.sumsoft.design.patterns.template;
 
/*
 * @author Sumith Puri
 */
public class SoftwareConsultants {
 
    public static void main(String args[]) {
 
        SoftwareProcessor softwareProcessor01 = new CProcessor();
        softwareProcessor01.deliverSoftware();
 
        SoftwareProcessor softwareProcessor02 = new JavaProcessor();
        softwareProcessor02.deliverSoftware();
    }
}

Iterator Pattern

The need to have a handle on a collection of elements without exposing its internal implementation is met by the Iterator Pattern. I would term this as a pure programming pattern in its own right. By utilizing this handle (Iterator), the client using the collection can easily process the same without any dependency on the internal logic.

In this example, ProductMenu holds a menu or list of ProductItem. This list and its usage should be implementation agnostic to the clients. Hence, the need for a ProductIterator that implements the generic Iterator interface. The createIterator() method of ProductMenu passes the array implementation of ProductItem to the constructor of ProductIterator.

The example can be run using ProductMenuTester.


 
package com.sumsoft.design.patterns.iterator;
 
/*
 * @author Sumith Puri
 */
public interface Iterator {
 
    public Object next();
    public boolean hasNext();
 
}
 
 
package com.sumsoft.design.patterns.iterator;
 
/*
 * @author Sumith Puri
 */
public class ProductIterator implements Iterator {
 
    ProductItem[] productItems;
    int marker = 0;
 
    public ProductIterator(ProductItem[] productItems) {
        this.productItems = productItems;
    }
 
    @Override
    public boolean hasNext() {
        boolean hasNext = false;
        if (marker < productItems.length) hasNext = true;
 
        return hasNext;
    }
 
    @Override
    public Object next() {
        ProductItem productItem = null;
        if (marker < productItems.length) productItem = productItems[marker++];
 
        return productItem;
    }
 
}
 
 
package com.sumsoft.design.patterns.iterator;
 
/*
 * @author Sumith Puri
 */
public class ProductMenu {
 
    ProductItem[] productItems;
    int maxSize = 0;
 
    public ProductMenu(int size) {
        maxSize = size;
        productItems = new ProductItem[size];
    }
 
    public ProductIterator createIterator() {
        return new ProductIterator(productItems);
    }
 
    public void addReplaceItem(int index, ProductItem productItem) {
        if (index > maxSize) System.out.println("Cannot Add as Index Increases MaxSize");
        else productItems[index] = productItem;
 
    }
}

State Pattern

The State Pattern defines a way to maintain various steps or states of the same machine or class. The word machine comes to mind easily because it is the simplest example of a real-world scenario where there is a need to operate the same object in steps or set states, with the transition from one step to the next defined by a single action (or multiple actions).

The example is a very crude but helpful one, that of an OnlineShopping site. The limitation of the site being that at any given point only a single item can be purchased and processed. The various states during the purchase and processing are SelectionState, PurchaseState, AuthoriseState, AssembleState (optional) and DispatchState. Each of these states is processed and followed in a sequential manner. OnlineShopping maintains an instance variable of each of these states and also a currentState variable. The various state methods that exist within OnlineShopping are selection(), purchase(), authorise(), assemble() and dispatch(). When the client calls these methods, the actual invocations are performed on the state implementation held in the currentState variable. All state implementations implement the State interface, which specifies the lifecycle methods.

ShoppingClient is the main class. Try adding your own states along with the required lifecycle method.


 
package com.sumsoft.design.patterns.state;
 
/*
 * @author Sumith Puri
 */
public interface State {
 
    public void purchase();
    public void authorise();
    public void assemble();
    public void dispatch();
    public void complete();
}
 
 
package com.sumsoft.design.patterns.state;
 
/*
 * @author Sumith Puri
 */
public class SelectionState implements State {
 
    OnlineShopping shopping;
 
    public SelectionState(OnlineShopping shopping) {
        this.shopping = shopping;
    }
 
    @Override
    public void assemble() {
        System.out.println("Cannot Assemble Unless Selected");
 
    }
 
    @Override
    public void authorise() {
        System.out.println("Cannot Authorise Unless Selected");
 
    }
 
    @Override
    public void dispatch() {
        System.out.println("Cannot Dispatch Unless Selected");
 
    }
 
    @Override
    public void purchase() {
        System.out.print("-> Purchase");
    shopping.setCurrentState(shopping.getPurchaseState());
 
    }
 
    @Override
    public void complete() {
        System.out.println("-> Complete");
        shopping.setCurrentState(shopping.getSelectionState());
    }
 
}
 
 
package com.sumsoft.design.patterns.state;
 
/*
 * @author Sumith Puri
 */
public class OnlineShopping {
 
    State currentState;
 
    SelectionState selectionState;
    PurchaseState purchaseState;
    AuthoriseState authoriseState;
    AssembleState assembleState;
    DispatchState dispatchState;
 
    public OnlineShopping() {
        selectionState = new SelectionState(this);
        purchaseState = new PurchaseState(this);
        authoriseState = new AuthoriseState(this);
        assembleState = new AssembleState(this);
        dispatchState = new DispatchState(this);
 
        currentState = selectionState;
    }
 
    /**
     * @return the currentState
     */
    public synchronized State getCurrentState() {
        return currentState;
    }
 
    /**
     * @param currentState the currentState to set
     */
    public synchronized void setCurrentState(State currentState) {
        this.currentState = currentState;
    }
 
    /**
     * @return the selectionState
     */
    public synchronized SelectionState getSelectionState() {
        return selectionState;
    }
 
    /**
     * @param selectionState the selectionState to set
     */
    public synchronized void setSelectionState(SelectionState selectionState) {
        this.selectionState = selectionState;
    }
 
    /**
     * @return the purchaseState
     */
    public synchronized PurchaseState getPurchaseState() {
        return purchaseState;
    }
 
    /**
     * @param purchaseState the purchaseState to set
     */
    public synchronized void setPurchaseState(PurchaseState purchaseState) {
        this.purchaseState = purchaseState;
    }
 
    /**
     * @return the authoriseState
     */
    public synchronized AuthoriseState getAuthoriseState() {
        return authoriseState;
    }
 
    /**
     * @param authoriseState the authoriseState to set
     */
    public synchronized void setAuthoriseState(AuthoriseState authoriseState) {
        this.authoriseState = authoriseState;
    }
 
    /**
     * @return the assembleState
     */
    public synchronized AssembleState getAssembleState() {
        return assembleState;
    }
 
    /**
     * @param assembleState the assembleState to set
     */
    public synchronized void setAssembleState(AssembleState assembleState) {
        this.assembleState = assembleState;
    }
 
    /**
     * @return the dispatchState
     */
    public synchronized DispatchState getDispatchState() {
        return dispatchState;
    }
 
    /**
     * @param dispatchState the dispatchState to set
     */
    public synchronized void setDispatchState(DispatchState dispatchState) {
        this.dispatchState = dispatchState;
    }
 
    public void purchase(String itemName) {
        System.out.print(itemName);
        currentState.purchase();
    }
 
    public void authorise() {
        currentState.authorise();
    }
 
    public void assemble() {
        currentState.assemble();
    }
 
    public void dispatch() {
        currentState.dispatch();
    }
 
    public void complete() {
        currentState.complete();
    }
}

Note: The snippets above explain the various design patterns’ core concepts. You may download the code from the link below and run them on your system for a more thorough understanding. You may also choose to modify the code with your own examples to cement your knowledge.

Here’s the GitHub repository for the code samples: https://github.com/sumithpuri/skp-code-marathon-pattaya

To understand the philosophical and historical perspective on the Gang of Four’s design patterns, I made a short 10-minute video (it was also my author audition for a learning site called PluralSight).

Coding Exercise

Find out where exactly (Examples) within the Core Java API or feature implementation are the above design patterns being used?

Watch for parts 3 and 4 of this series. Until then, Happy Design Patterns with Core Java!

Latest Posts

Related Stories