The Factory Pattern - Design Patterns meet the Frontend

coly010

Colum Ferry

Posted on December 9, 2019

The Factory Pattern - Design Patterns meet the Frontend

Picture this. A Car Dealership selling Cars šŸš—. Suddenly, they want to branch out and sell Trucks šŸš›. You had initially programmed the order and sale system to handle Cars. What do you do now? Do you duplicate the majority of the business logic in the system to also handle trucks specifically?

Sure, it's a somewhat quick win to do this. A short while later, the Dealership decides it's gonna start selling Motorbikes.

šŸ¤¦ Oh no. More code duplication? What happens if the order system needs to change, do we need to update it now in three places!!?

We've all been there. It's hard to predict when this situation might occur. But when it does, know that there is a solution that initially might require a bit of refactoring but will surely mean a more maintainable solution, especially when the Dealership says it's gonna start selling Boats! šŸ›„ļø

In this article, we will discuss:

  • šŸ’Ŗ A Solution - The Factory Pattern
  • šŸ¤” When should I use it?
  • šŸ¤Æ Some Advantages and Disadvantages
  • ā“ Where is it being used in the Frontend World?
  • šŸ­ Let's see an example!

šŸ’Ŗ A Solution - The Factory Pattern

The Factory Pattern is a creational design pattern that adds an abstraction layer over common base behaviour between multiple objects of a generic type.
The client code, the code that will use this layer, does not need to know the specifics of the implementation of the behaviour, as long as it exists.

If we take our Car Dealership turned Multi-Vehicle Dealership example above we can see that the common ground between the Cars, Trucks and Boats are that they are all Vehicles. The Order System within the Dealership only needs to work with a base Vehicle, it doesn't need to know the specifics about the Vehicle being processed.

Let's take a quick look at a UML Diagram to illustrate this:

The Factory Pattern

As we can see from the diagram, the system contains concrete implementations of the Vehicle interface. The OrderSystem doesn't know, or need to know, what these concrete implementations are, it simply relies on the VehicleFactory to create and return them when required, therefore decoupling our OrderSystem from the Vehicles the Dealership wants to sell! šŸš€šŸš€šŸš€

They can branch out to as many Vehicles as they like now and we only ever have to create a new implementation of the Vehicle interface and update our VehicleFactory to create it! šŸ”„šŸ”„šŸ”„

šŸ¤” When should I use it?

There are a few situations, other than the one describe above where this pattern fits perfectly:

  • Any situation where at or during runtime you do not know the exact type or dependency a specific portion of your code needs to work with.
  • If you are developing a library, using the Factory Pattern allows you to provide a method for consuming developers to extend its internal components without requiring access to the source itself!
  • If you need to save system resources, you can use this Pattern to create an Object Pool, where new objects are stored when they do not already exist, and will be retrieved from when they do exist, instead of creating a new one.

šŸ¤Æ Some Advantages and Disadvantages

Advantages:

  • It avoids tight coupling between the Consumer of the Factory and the Concrete Implementations.
  • In a way it meets the Single Responsibility Principle by allowing the creation code to be maintained in one area.
  • It also meets the Open/Closed Principle by allowing new Concrete Implementations to be added without breaking the existing code.

Disadvantages:

  • It can increase the complexity and maintainability of the codebase as it requires a lot of new subclasses for each Factory and Concrete Implementation

ā“ Where is it being used in the Frontend World?

Surprisingly (well maybe not), Angular allows the usage of Factories in their Module Providers. Developers can provide dependencies to modules using a factory, which is extremely useful when information required for the provider is not available until Runtime.

You can read more about them on the Angular Docs for Factory Providers.

šŸ­ Let's see an example!

A great example for this in the Frontend is cross-platform UIs.

Imagine having a cross platform app that shows a Dialog Box. The app itself should allow a Dialog to be rendered and hidden. The Dialog can render differently on a mobile app than it does on desktop. The functionality however, should be the same. The app can use a factory to create the correct Dialog at runtime.

For this example, we are going to use TypeScript to create two implementations of a Dialog, a MobileDialog and a DesktopDialog. The app will use the User Agent String to determine if the app is being viewed on a desktop or mobile device and will use the Factory to create the correct Dialog.

Note: Normally, it is more ideal to develop one responsive Dialog, however, this is an example to illustrate the Factory Pattern.

Let's start by creating a base Dialog Interface



interface Dialog {
    template: string;
    title: string;
    message: string;
    visible: boolean;

    hide(): void;
    render(title: string, message: string): string;
}


Enter fullscreen mode Exit fullscreen mode

This interface defines the common behaviour and state that any Concrete Implemenation will adhere to.
Let's create these Concrete Implementations:



class MobileDialog implements Dialog {
    title: string;
    message: string;
    visible = false;

    template = `
        <div class="mobile-dialog">
            <h2>${this.title};</h2>
            <p class="dialog-content">
              ${this.message}
            </p>
        </div>
    `;

    hide() {
        this.visible = false;
    }

    render(title: string, message: string) {
        this.title = title;
        this.message = message;
        this.visible = true;

        return this.template;
    }
}

class DesktopDialog implements Dialog {
    title: string;
    message: string;
    visible = false;

    template = `
        <div class="desktop-dialog">
            <h1>${this.title};</h1>
            <hr>
            <p class="dialog-content">
              ${this.message}
            </p>
        </div>
    `;

    hide() {
        this.visible = false;
    }

    render(title: string, message: string) {
        this.title = title;
        this.message = message;
        this.visible = true;

        return this.template;
    }
}


Enter fullscreen mode Exit fullscreen mode

There are only slight variations in this functionality, and you may argue an Abstract Class would be a better fit for this as the render and hide methods are the same. For the sake of this example, we will continue to use the Interface.

Next we want to create our Factory:



class DialogFactory {
    createDialog(type: 'mobile' | 'desktop'): Dialog {
        if (type === 'mobile') {
            return new MobileDialog();
        } else {
            return new DesktopDialog();
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

Our Factory takes a type and will subsequently create the correct implementation of the Dialog.

Finally our App needs to consume our Factory:



class App {
    dialog: Dialog;
    factory = new DialogFactory();

    render() {
        this.dialog = this.factory.createDialog(isMobile() ? 'mobile' : 'desktop');
        if (this.dialog.visible) {
            this.dialog.render('Hello World', 'Message here');
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

Looking at the code above, we can see that the App does not need to know about the Concrete Implementations, rather, that logic is the responsibility of the DialogFactory.

Hopefully, this code example has helped to clarify the Factory Pattern and it's potential usage in the Frontend World.


Personally, I understand the concept and the advantages of this Pattern, but I do not like the focus and reliance on Inheritance that it requires for it's implementation.

Feel free to discuss any other examples or your own opinions of this Pattern, as I'm still undecided on it.
If you have any questions, feel free to ask below or reach out to me on Twitter: @FerryColum.

šŸ’– šŸ’Ŗ šŸ™… šŸš©
coly010
Colum Ferry

Posted on December 9, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related