DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Protect Your Invariants!
  • Bridge Design Pattern in Java: An Intro, Real-time Examples, Class/Seq Diagram, and Implementation
  • Adapter Design Pattern in Java [Language Translator] - Introduction, Example, and Implementation
  • Adapter Design Pattern in Java - Introduction, Class/Sequence Diagram, and Implementation

Trending

  • Measuring the Impact of AI on Software Engineering Productivity
  • Comparing SaaS vs. PaaS for Kafka and Flink Data Streaming
  • How to Practice TDD With Kotlin
  • Scalability 101: How to Build, Measure, and Improve It
  1. DZone
  2. Coding
  3. Java
  4. GoF Design Patterns Using Java (Part 2)

GoF Design Patterns Using Java (Part 2)

Continue your understanding of design patterns by implementing the Adapter, Facade, Template, Iterator, and State patterns using Java.

By 
Sumith Puri user avatar
Sumith Puri
·
Feb. 08, 17 · Tutorial
Likes (20)
Comment
Save
Tweet
Share
29.7K Views

Join the DZone community and get the full member experience.

Join For Free

GitHub Link (Code Samples from the Article & More)
https://github.com/sumithpuri/skp-code-marathon-pattaya


My concluding write-up on design patterns, with some examples. Please first check out Part-1 of this article under 'GoF Design Patterns Using Java (Part 1).'

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


I came up with my own examples to understand 'Design Patterns' further. Try downloading the code and see if it helps you in comprehending these in a better way. You may refer to (bookmark) this article as Quick Reference/Cheatsheet when you want to quickly revisit each of them.

Adapter Pattern

What can you do if you need to use an Asian hairdryer in a European country, each with different socket types? I would us an adapter! As in real life, when we want to plug and play with similar but incompatible interfaces, we use the Adapter. The Adapter adapts the adaptee to the desired interface by composing the adaptee object and inheriting the desired interface — or through multiple inheritance.

The attached example is a real-world computer scenario where I want to plug in an external hard drive (pre-USB era!), a SeagateDrive of interface type SeagateGeneric, to an incompatible computer, a SamsungComputer of type Computer. SeagateGeneric provides read() and write() methods for these 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 adaptee.

Only the core concept is provided in the snippets below. You can download the sample code for the complete code/application.

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

    }
}


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

Facade Pattern

Consider a scenario where we require multiple method invocations on various classes to achieve a desired functionality. Also, consider that this 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 with respect to 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 with respect to the individual implementations. Remote method invocation patterns like SessionFacade (EJB) adapt from here to improve the overall performance and lower complexity.

The example attached is a very simple scenario of an InvoiceManagerFacade which has addInvoice() and deleteInvoice() methods. To achieve the desired result, each of these methods encapsulates the method invocations from 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());
    }

}


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

Template Pattern

Imagine a real-world scenario where a factory is creating both aluminum nails and screws. Though the machine has to 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 re-use algorithms for various implementations with different or slightly different outcomes.

In the attached 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.

Only the core concept is provided in the snippets below. You can download the sample code for the complete code/application.

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 adding your own processor.

Iterator Pattern

The need to have a handle to 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 the attached 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 which implements the generic Iterator interface. The createIterator() method of ProductMenu passes the array implementation of ProductItem to the constructor of ProductIterator.

Only the core concept is provided in the snippets below. You can download the sample code for the complete code/application.

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 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 attached is a very crude but helpful one — that of an online shopping site, aptly named OnlineShopping. The limitation of this site is 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 a 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.

Only the core concept is provided in the snippets below. You can download the sample code for the complete code/application.

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


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

Note: The snippets above explain the various design patterns' core concepts. You may download the code from each of the links above 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. 

Design Java (programming language) Interface (computing) Implementation Computer Concept (generic programming) State pattern Snippet (programming) Download

Published at DZone with permission of Sumith Puri. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Protect Your Invariants!
  • Bridge Design Pattern in Java: An Intro, Real-time Examples, Class/Seq Diagram, and Implementation
  • Adapter Design Pattern in Java [Language Translator] - Introduction, Example, and Implementation
  • Adapter Design Pattern in Java - Introduction, Class/Sequence Diagram, and Implementation

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!