Design Patterns: Factory - Getting Started with Typescript
Tags: typescript, webdev, beginners, oop
Published: 2023-03-15 21:27:19

## What is a Design Pattern?  
<p> </p>

Hi devs. We will start talking about design patterns for software development.  In day life as a programmer, we face several refactoring issues, clean code reusability and performance problems to make sure our sofware is scalable.

All these problems were shared by dev community for a long time and the result of all discussions was the creation of several paradigms to build a code.

It was created, for example, the object-oriented programming, the four pillars of OOP, the SOLID principles and the design patterns.

Design patterns are patterns of software development with the aim to make code scalable and improve software quality. The standards guarantee ways to solve problems of clarity and code maintainability.

Remember, the patterns are not code blocks to add to your project. They are ways to coding and because of that, the patterns are usable to several languages. We're going to code with typescript, but you can choose the language you like the most.


## Factory

The factory, or factory method, is one of the creational patterns (Factory, Abstract Factory, Builder, Prototype and Singleton), so, it's a pattern relative to object instances. It proposes the coding of a method to instance classes objects. 

Using factory method, your code will no longer instance class objects with `new`. Your code is going to call the factory method and this one will return an instantiated object called **product**.


## What's the advantage of using the Factory method?

First of all, you uncouple instances from your code. So the business rule needed by a product is not explained to the rest of your code.

Second, if you use the factory method, you can add features to all processes involving the instance of objects. Without factory, you need to add a change to all 'new' method called.

You can think about adding the feature in constructor but, in this way, you can code against three SOLID principles at once (S, O and D). Let's check examples:

You are coding a car shop management system and in your code you have the entity `Car`. This class is fully of business rules about car, including taxes calculation and car registration documents.

So we have to develop a new feature: 'When a new car is instantiated, we must log this event'. You must create a `Log` class responsible for logging these data in a file or database.

If we instantiate an `Log` object  at `Car` class constructor, we are coding against the **single-responsibility principle** because the `Car` class must be responsible only by cars.

We don't use the **Open-closed principle** because a class should be open for extension and close for modification. In this case, we are changing the constructor.

And we don't use the **dependency inversion principle** because the `Car` class can't be used due to the high coupling to `Log` class. If you want to use the `Car` class in other system, you should to code the `Log` class.

The third advantage is that you can return a previously created object instead of instantiating a new one. In this case, we use another creational pattern called `Singleton`.


---

## Let's Code

We are developing an Airline software and the first class is `Airplane`. For a while, we won't code complex methods and numerous attributes. We'll have only `prefix`, `manufacturer` and `aircraft` attributes.

We will have the `getters` methods and all attributes will be assigned in constructor. Our `Airplane` class implements the `IAirplane` interface.


![Airplane Class](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bgoe310p1ygluy6l9sgp.png)

```ts
interface IAirplane {
    prefix: string;
    manufacturer: string;
    aircraft: string;
}

class Airplane implements IAirplane {
    constructor(private _prefix: string,
        private _manufacturer: string,
        private _aircraft: string) {}
    
    get prefix(): string {
        return this._prefix
    }

    get manufacturer(): string {
        return this._manufacturer;
    }

    get aircraft(): string {
        return this._aircraft;
    }

}
```
For now on, every time we use an object `Airplane` we would call the `new` method:


```ts
const embraerE195 = new Airplane('PR-ABC','Embraer','E195');
```

However, we're going to use the factory pattern. The implementation is the creation of a new class called `AirplaneFactory`. This class must have the factory method called `create` which it will instantiate a new `Airplane` product. So, in our code, the factory class is going to be the only one that we can to instantiate with the `new` method.

Some samples suggest that factory method and factory class are statics. This is a wrong way to code the factory that prevents it to be extended. We will check about creating abstract and concrete products.


![Airplane and Factory classes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cjrfwp319d9ltsvdlh8k.png)

In factory class and factory method, we code:

```ts
class AirplaneFactory {
    public create (prefix: string, manufacturer: string, aircraft: string): Airplane {
        return new Airplane(prefix, manufacturer, aircraft);
    } 
};

```
and to create a new `Airplane`:

```ts
const airplaneFactory = new AirplaneFactory();

const embraerE195 = airplaneFactory.create('PR-ABC','Embraer','E195');
```

Note that the factory method must always have a product of the same type as the business rule class, even if it is an abstract product.

---


## Abstracts and concrete factories

We will increase the complexity. In our business rule, from now on, the airplane is an abstract entity to create passenger airplanes and cargo airplanes. 

In the diagram, we have the classes `PassengerAirplane` and `CargoAirplane` extending the abstract class `Airplane` and the interfaces `IPassengerAirplane` and `ICargoAirplane`.



![Abstract and concrete classes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36dsptrjl5a1e1nji5wl.png)

Now we have the abstract class for `Airplane`

```ts
abstract class Airplane implements IAirplane {
    constructor(private _prefix: string,
        private _manufacturer: string,
        private _aircraft: string) {}
    
    get prefix(): string {
        return this._prefix
    }

    get manufacturer(): string {
        return this._manufacturer;
    }

    get aircraft(): string {
        return this._aircraft;
    }
}
```
and the concrete classes

```ts
interface IPassengerAirplane extends IAirplane {
    passengerCapacity: number;
    buyTicket(): void;
}


class PassengerAirplane extends Airplane implements IPassengerAirplane {
    
    constructor(prefix: string, manufacturer: string, aircraft: string, private _passengerCapacity: number) {
        super(prefix, manufacturer, aircraft);
    }
    
    get prefix(): string {
        return super.prefix
    }

    get manufacturer(): string {
        return super.manufacturer;
    }

    get aircraft(): string {
        return super.aircraft;
    }

    get passengerCapacity(): number {
        return this._passengerCapacity;
    }

    public buyTicket(): void {
        console.log(`New ticket emitted to ${this.manufacturer} ${this.aircraft} - Prefix: ${this.prefix}`);
    }
}

```

```ts
interface ICargoAirplane extends IAirplane {
    payload: number;
    loadCargo(weight: number)
}

class CargoAirplane extends Airplane implements ICargoAirplane {
    constructor(prefix: string, manufacturer: string, aircraft: string, private _payload: number) {
        super(prefix, manufacturer, aircraft);
    }

    get prefix(): string {
        return super.prefix
    }

    get manufacturer(): string {
        return super.manufacturer;
    }

    get aircraft(): string {
        return super.aircraft;
    }

    get payload(): number {
        return this._payload;
    }

    public loadCargo(weight: number){
        console.log(`${weight} loaded to ${this.manufacturer} ${this.aircraft} - Prefix: ${this.prefix}`);
    }
}

```

Now we should fix the factory method. At this moment, we have a factory method to create an `Airplane` product. We must keep the concrete products as a `Airplane` product but extending to a `PassengerAirplane` or `CargoAirplane`. Because of this, we must have two concrete factories extending from an abstract factory.

The factory method must be an abstract method in the abstract class. The concrete methods must be implemented according to yours definitions.




![Abstract and concrete factories](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yne77bsy9xayers6ejtx.png)

The concrete factories implement the `create` method. Even the two possible products are different, both of them implements the `IAirplane` interface. This is a sample to not create a static factory method.

Let's code

```ts
abstract class AirplaneFactory {
    public abstract create (prefix: string, 
       manufacturer: string, 
       aircraft: string, 
       payload: number, 
       passengerCapacity: number): Airplane
};

class PassengerAirplaneFactory extends AirplaneFactory {
    public create (prefix: string, 
       manufacturer: string, 
       aircraft: string, 
       passengerCapacity: number): PassengerAirplane {
        return new PassengerAirplane(prefix,
           manufacturer,
           aircraft,
           passengerCapacity);
    } 
};

class CargoAirplaneFactory extends AirplaneFactory {
    public create (prefix: string,
       manufacturer: string, 
       aircraft: string, 
       payload: number): CargoAirplane {
        return new CargoAirplane(prefix,
           manufacturer,
           aircraft,
           payload);
    }
};

```



```ts
const passengerAirplaneFactory = new 
    PassengerAirplaneFactory();

const cargoAirplaneFactory = new 
    CargoAirplaneFactory();

const E195 = passengerAirplaneFactory
    .create('PR-ABC', 
        'Embraer', 
        'E195', 
         118);

const KC390 = cargoAirplaneFactory
    .create('PR-DEF', 
        'Boeing', 
        'B747', 
         137);

E195.buyTicket();
KC390.loadCargo(100);

```

We create two objects, one called `E195` created with the concrete factory `PassengerAirplaneFactory` and another one called `KC390` created with the concrete factory `CargoAirplaneFactory`. We called the methods `buyTicket` and `loadCargo` in spite of both of them are an `Airplane` product.

If you think that we can code a concrete factory capable to create both products, the answer is yes.

In this case, we have another pattern, the **Abstract Factory** that is responsible to create families of products. But remember, an abstract factory class is not an abstract factory.

---

## Tests

We can test our factory method to check if the created products are instances of domain classes. We will use `Jest` but you can use the testing library of your choice.

First of all, I will test the `PassengerAirplaneFactory`


```ts
let passengerAirplaneFactory;
    beforeEach(() => {
        passengerAirplaneFactory = new 
             PassengerAirplaneFactory();
    });
```

Then, I will test:

- If the `PassengerAirplaneFactory` is an instance of it.
- If the `PassengerAirplaneFactory` is an instance of `AirplaneFactory`.
- If the `PassengerAirplaneFactory` returns an `Airplane` product and a `PassengerAirplane` product.
- And if the `PassengerAirplaneFactory` doesn't return a `CargoAirplaneFactory` product.


```ts

describe('Passenger airplane factory', () => {

    let passengerAirplaneFactory;
    beforeEach(() => {
        passengerAirplaneFactory = new 
            PassengerAirplaneFactory();
    });

    it('is a instance of Airplane factory', () => {             
        expect(passengerAirplaneFactory)
            .toBeInstanceOf(AirplaneFactory);
    });

    it('is a instance of Passenger airplane factory', () => {
        expect(passengerAirplaneFactory)
            .toBeInstanceOf(PassengerAirplaneFactory);
    });
    it('creates a airplane and passenger 
    airplane product', () => {
        const E195 = passengerAirplaneFactory
            .create('PR-ABC', 
                'Embraer', 
                'E195', 
                 118); 
        expect(E195).toBeInstanceOf(Airplane);
        expect(E195).toBeInstanceOf(PassengerAirplane);
    });
    it('does not create a cargo airplane product', () => {
        const E195 = passengerAirplaneFactory
            .create('PR-ABC', 
                'Embraer', 
                'E195', 
                118); 
        expect(E195).not.toBeInstanceOf(CargoAirplane);
    });
});

```

To test `CargoAirplaneFactory`, we have conditionals similar to `PassengerAirplaneFactory`.

```ts

describe('Cargo airplane factory', () => {

    let cargoAirplaneFactory;
    beforeEach(() => {
        cargoAirplaneFactory = new CargoAirplaneFactory();
    });

    it('is a instance of Airplane factory', () => {    
        expect(cargoAirplaneFactory)
                .toBeInstanceOf(AirplaneFactory);
    });

    it('is a instance of Cargo airplane factory', () => {            
        expect(cargoAirplaneFactory)
            .toBeInstanceOf(CargoAirplaneFactory);
    });

    it('creates a airplane and cargo airplane product', () => 
    {
        const B747 = cargoAirplaneFactory
            .create('PR-DEF', 'Boeing', 'B747', 137); 
        expect(B747).toBeInstanceOf(Airplane);
        expect(B747).toBeInstanceOf(CargoAirplane);
    });

    it('does not create a passenger airplane product', () => 
    {
        const B747 = cargoAirplaneFactory
              .create('PR-DEF', 'Boeing', 'B747', 137); 
        expect(B747).not.toBeInstanceOf(PassengerAirplane);
    });
});

```

Now we can check the results of our test.


![Factory tests](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3j741pbzqjwzk4cwqre8.png)

Finally, this is the `Factory` pattern. I hope this was useful to help you.

Happy studying!

<h3 style="color:#000066">Danilo Silva</h3>
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tj4i67b3yid2wlx90oky.png" />

Software developer with experience at clean code, patterns and telecommunication hardware and software development.

<a href="https://www.linkedin.com/in/danilo-silva-44518956/">Linkedin</a>
<a href="https://github.com/Danilo-Oliveira-Silva">Github</a>
<a href="mailto:danilo.o.s@hotmail.com">E-mail</a>