GoF Design Patterns Using Java (Part 2)

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

My concluding article on design patterns, with some examples. Please see Part 1 of this article first under “GoF Design Templates Using Java (Part 1)”.

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 offered my own examples to better understand “design patterns”. Try downloading the code and see if it helps you understand them better. You can refer to (bookmark) this article as a quick reference / cheat sheet when you want to quickly review each one.

Adapter model

What can you do if you need to use an Asian hair dryer in a European country, each with different types of plugs? I would have us an adapter! As in real life, when we want to plug in and play with similar but incompatible interfaces, we use the adapter. The adapter adapts the suit to the desired interface by composing the adapted object and inheriting the desired interface – or by multiple inheritance.

The attached example is a real computer scenario where I want to plug an external hard drive (pre-USB era!), SeagateDrive of SeagateGeneric interface type, to incompatible computer, SamsungComputer of Computer type. SeagateGeneric provides read () and write () methods for these purposes, which must be matched to the actual bufferData (), flushData (), and purgeData () methods on the computer. Note that there is no equivalent of purgeData (). The ideal way to handle this scenario is to throw an exception every time this method is invoked on the hard drive, as it would in the real world. The adapter to perform the translation in this scenario is the SeagateAdapter, which implements the Computer interface. It wraps a SeagateGeneric instance reference and adapts it to the computer interface. Each time a bufferData () method is invoked on the computer interface, it actually requires three read () invocations on the SeagateGeneric implementation to match the standards on the computer. These types of translations are performed by the adaptive.

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.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...");

    }
}


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

Facade pattern

Consider a scenario where we need multiple method calls on different classes to get the functionality we want. Also, be aware that this feature is used multiple times in your code. If you are considering an option where you will make direct calls, you will inevitably end up with code maintenance issues and tightly coupled code. If these summons are distant, the performance will be worse. This is where the facade comes into play, in which 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 compared to individual implementations. Remote method call models such as SessionFacade (EJB) scale from here to improve overall performance and reduce complexity.

The attached example is a very simple scenario of an InvoiceManagerFacade which has the addInvoice () and deleteInvoice () methods. To achieve the desired result, each of these methods encapsulates the method calls of the OrderManager, LedgerManager, and BillingManager classes.

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

}

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

Model template

Imagine a real-life scenario where a factory creates both nails and aluminum screws. Although the machine must create them both through similar processes, the way in which certain steps are implemented may vary in each of them. When we think of such scenarios in software, we use the model template. The model template defines a way to reuse algorithms for various implementations with different or slightly different results.

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

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

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

Iterator model

The need to have a handle on a collection of elements, without exposing its internal implementation, is satisfied by the Iterator model. I would call this a pure programming model in its own right. Using this handle (Iterator), the client using the collection can easily process the same without any dependency on internal logic.

In the attached example, ProductMenu contains a menu or a list of ProductItem. This list and its use should be implementation independent for 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.

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.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;

    }
}

The example can be run using ProductMenuTester.

State model

The State model defines a way to maintain various stages or states of the same machine or class. The word machine easily comes to mind because it is the simplest example of a real world scenario where it is necessary to make the same object work in defined stages or states – with the transition from a step to the next defined by a single action (or several actions).

The attached example is very crude but useful – that of an online shopping site, aptly named OnlineShopping. The limitation of this site is that at any one time only one item can be purchased and processed. The different states during purchase and processing are SelectionState, PurchaseState, AuthoriseState, AssembleState (optional), and DispatchState. Each of these states is processed and tracked sequentially. OnlineShopping maintains an instance variable of each of these states, as well as a currentState variable. The different state methods that exist in OnlineShopping are selection (), purchase (), authorize (), assemble (), and dispatch (). When a client calls these methods, the actual calls are made to the state implementation contained in the currentState variable. All state implementations implement the State interface, which specifies lifecycle methods.

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


ShoppingCustomer
is the main class. Try to add your own states with the required lifecycle method.

To note: The above excerpts explain the basic concepts of the different design patterns. You can download the code from 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.


Source link

Abdul J. Gaspar

Leave a Reply

Your email address will not be published.