Code Review Videos > Design Patterns > Factory Pattern Example in TypeScript

Factory Pattern Example in TypeScript

The simple factory pattern can be useful if you want to create objects of a specific type based on some given inputs, but you don’t want the client code to have to know about the specific implementation details of each object type.

In this example, the VehicleFactory class acts as a central point in the code – the factory – for creating Vehicle objects.

interface Vehicle { move(): string; } class Car implements Vehicle { move() { return "Driving a car..."; } } class Bike implements Vehicle { move() { return "Riding a bike..."; } } export class VehicleFactory { static createVehicle(type: string): Vehicle { switch (type) { case "car": return new Car(); case "bike": return new Bike(); default: throw new Error("Invalid vehicle type"); } } } const car = VehicleFactory.createVehicle("car"); car.move(); // Outputs: 'Driving a car...' const bike = VehicleFactory.createVehicle("bike"); bike.move(); // Outputs: 'Riding a bike...'
Code language: TypeScript (typescript)

The createVehicle method takes a string parameter that specifies the type of vehicle to create, and it returns an object of the corresponding type.

These createVehicle methods is static so we do not need to create a new instance of the VehicleFactory before we can call / use this method.

The client code can then call the createVehicle method of the factory and pass in the desired vehicle type, and the factory will handle the rest.

This works as a basic starting point, so lets add some unit tests and then see if we can improve the code.

Unit Tests for Our Factory

Life isn’t worth living if you haven’t added some unit tests.

Here are some example unit tests for the above implementations:

import { VehicleFactory } from "./factory"; describe("VehicleFactory", () => { it("should create a car", () => { const vehicle = VehicleFactory.createVehicle("car"); expect(vehicle.move).toBeDefined(); expect(vehicle.move()).toEqual("Driving a car..."); }); it("should create a bike", () => { const vehicle = VehicleFactory.createVehicle("bike"); expect(vehicle.move).toBeDefined(); expect(vehicle.move()).toEqual("Riding a bike..."); }); it("should throw an error for an invalid vehicle type", () => { expect(() => VehicleFactory.createVehicle("boat")).toThrowError( "Invalid vehicle type" ); }); });
Code language: TypeScript (typescript)

These tests cover three test cases.

The first two tests behave almost identically and could almost certainly be refactored to be more streamlined if further VehicleType‘s were introduced in to the system.

Some people disagree with testing .toBeDefined, as if the method were not defined then the test on lines 9 and 16 would fail anyway. Personally I see no harm in adding this.

The more interesting test is when expecting an error. This one tricked me for a while when learning to use Jest. Note that the expect code that tests createVehicle is itself wrapped in a function. That function is then checked by Jest to assert against the error message.

With some passing tests we can now improve the code with more confidence.

Refactoring #1

A possible improvement here is to use an enum for the allowable types of vehicle that exist in our system. In my opinion, enum‘s or other similar constructs (an object with set keys, symbols, etc) make the code easier to read and less likely to contain errors.

export enum VehicleType { Car = "car", Bike = "bike", }
Code language: TypeScript (typescript)

We could then use the new enum values in our tests:

import { VehicleFactory, VehicleType } from "./factory"; describe("VehicleFactory", () => { it("should create a car", () => { const vehicle = VehicleFactory.createVehicle(VehicleType.Car); expect(vehicle.move).toBeDefined(); expect(vehicle.move()).toEqual("Driving a car..."); }); it("should create a bike", () => { const vehicle = VehicleFactory.createVehicle(VehicleType.Bike); expect(vehicle.move).toBeDefined(); expect(vehicle.move()).toEqual("Riding a bike..."); });
Code language: TypeScript (typescript)

We exported the enum from the implementation code, so we can import it (line 1) in our tests.

Then we replace the literal strings of car and bike with the two enum values we have restricted down too.

At this points the tests should still pass.

Refactoring #2

Introducing the enum was a good start.

However a consumer of our VehicleFactory.createVehicle method can still pass in any string value they like:

static createVehicle(type: string): Vehicle {
Code language: JavaScript (javascript)

Given we have the more specific VehicleType available, we should use this in place of the string type in this method signature:

static createVehicle(type: VehicleType): Vehicle {
Code language: JavaScript (javascript)

However, this ‘breaks’ things:

So, what’s the issue here?

Well, we have told TypeScript that our VehicleFactory.createVehicle method expects only values from our VehicleType enum options. And then we have passed in some literal string values.

From the screenshot above you can probably understand why boat is underlined in red in the test file.

But why are the strings of car and bike seen as TypeScript errors when they match the string values in the enum?

TS2345: Argument of type '"car"' is not assignable to parameter of type 'VehicleType'.
Code language: JavaScript (javascript)

To the very best of my understanding (aka, I may be wrong), the issue here is that the string enum is a more specific type of the string itself.

So VehicleType.Car (the string enum value of “car”) is a subtype of the string literal “car”.

TypeScript can compare VehicleType.Car to "car" but it cannot compare "car" to VehicleType.Car.

If that doesn’t make a great deal of sense then maybe this explains it better. If not, I would say do not worry too much as this is a detail that is interesting to know, but will not directly impact you in 99% of your day to day usage of TypeScript. At least, it never has for me.

Exhaustive Switch

Fixing that failing test is actually really challenging.

Think about it for a moment.

Our test says that when we give an unexpected parameter to our VehicleFactory.createVehicle method, our code should throw an error.

Our implementation is set up that way, and we saw before that the tests do pass.

But now we have provided a more restrictive function signature:

static createVehicle(type: VehicleType): Vehicle {
Code language: JavaScript (javascript)

We control the enum and we therefore know we only ever have two possible values. Car, or Bike.

TypeScript knows this, too.

So do we even need the default case in our switch ?

static createVehicle(type: VehicleType): Vehicle { switch (type) { case "car": return new Car(); case "bike": return new Bike(); default: throw new Error("Invalid vehicle type"); } }
Code language: TypeScript (typescript)

The answer here is … yes 😧 then no 🤗

Let’s remove the third unit test case entirely, and the default clause:

export class VehicleFactory { static createVehicle(type: VehicleType): Vehicle { switch (type) { case "car": return new Car(); case "bike": return new Bike(); } } }
Code language: JavaScript (javascript)

This makes TypeScript sad:

TS2366: Function lacks ending return statement and return type does not include 'undefined'.
Code language: JavaScript (javascript)

And the tests still fail:

The issue here is subtle.

static createVehicle(type: VehicleType): Vehicle { switch (type) { case "car": return new Car(); case "bike": return new Bike(); } }
Code language: TypeScript (typescript)

We saw that TypeScript can convert the enum value to the matching string, so technically this code does work.

The problem is that we told TypeScript to expect our method to return a Vehicle, and because we have only checked two literal string cases, what happens if anything else is passed in? Something new added to the VehicleType perhaps?

Well, that would not match the two specified case statements, and we haven’t defined what to do in that case. So our system would blow up.

As I said, the fix is subtle, but also really simple:

static createVehicle(type: VehicleType): Vehicle { switch (type) { case VehicleType.Car: return new Car(); case VehicleType.Bike: return new Bike(); } }
Code language: TypeScript (typescript)

We switched to use the enum values in our case clauses, and therefore we no longer need the default.

This is called an exhaustive switch statement. We have covered all the possible permutations.

Our tests therefore are now as simple as:

import { VehicleFactory, VehicleType } from "./factory"; describe("VehicleFactory", () => { it("should create a car", () => { const vehicle = VehicleFactory.createVehicle(VehicleType.Car); expect(vehicle.move).toBeDefined(); expect(vehicle.move()).toEqual("Driving a car..."); }); it("should create a bike", () => { const vehicle = VehicleFactory.createVehicle(VehicleType.Bike); expect(vehicle.move).toBeDefined(); expect(vehicle.move()).toEqual("Riding a bike..."); }); });
Code language: PHP (php)

And the last question then, what happens if we add a new value to the VehicleType enum?

We added VehicleType.Bus and immediately our tests break and the issue reoccurs.

Real World

I figured I would add this in as it’s rare for me to come across class based TypeScript code out there in the real world. It is probably more prevalent in certain frameworks (Angular?) than it is in React projects that I tend to work on.

Here’s two variants you may see that both satisfy the test cases:

interface Vehicle { move(): string; } const car: Vehicle = { move: () => "Driving a car...", }; const bike: Vehicle = { move: () => "Riding a bike...", }; export class VehicleFactory { static createVehicle(type: VehicleType): Vehicle { switch (type) { case VehicleType.Car: return car; case VehicleType.Bike: return bike; } } }
Code language: TypeScript (typescript)

And / or:

type Vehicle = { move(): string; }; const car: Vehicle = { move: () => "Driving a car...", }; const bike: Vehicle = { move: () => "Riding a bike...", }; export class VehicleFactory { static createVehicle(type: VehicleType): Vehicle { switch (type) { case VehicleType.Car: return car; case VehicleType.Bike: return bike; } } }
Code language: TypeScript (typescript)

The differences are subtle – type versus interface – which is something of an interesting debate in itself.

Note that this code is only comparable because the implementations are simple.

The nice part about factories are they can hide away a lot of complexity – if you were simply creating one method objects like the above, a factory may very possibly be overkill.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: