December 2, 2019











As discussed in the previous part of this series, there are many design patterns out there. Some of them fit well into the JavaScript language, and they are often used. In this article, we discuss the factory method pattern and show some of its use-cases. We also implement it in TypeScript.

There seems to some confusion regarding what the Factory pattern in JavaScript is. Let’s try to look up the “JavaScript factory” term in the search bar.

Factory functions

One of the approaches that might come up are the factory functions.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const createPerson = ( { firstName , lastName } ) = > ( { firstName , lastName , speak : ( ) = > console . log ( ` My name is $ { firstName } $ { lastName } ` ) } ) ; const person = createPerson ( { firstName : 'John' , lastName : 'Smith' } ) ; console . log ( person . speak ( ) ) ; // My name is John Smith

The job of a factory function is to return an object. It might be an alternative to creating classes. A useful addition to those are mixins that you can use to make some parts of your factory functions reusable.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const withNames = ( { firstName , lastName } ) = > ( { firstName , lastName , speak : ( ) = > console . log ( ` My name is $ { firstName } $ { lastName } ` ) } ) ; const createProgrammer = ( { firstName , lastName , programmingLanguage } ) = > ( { . . . withNames ( { firstName , lastName } ) , programmingLanguage } ) ; const createTeacher = ( { firstName , lastName , subject } ) = > ( { . . . withNames ( { firstName , lastName } ) , subject } ) ;

1 2 3 4 5 6 7 8 9 10 11 const programmer = createProgrammer ( { firstName : 'John' , lastName : 'Smith' , programmingLanguage : 'JavaScript' } ) ; const teacher = createTeacher ( { firstName : 'Jacob' , lastName : 'Williams' , subject : 'Maths' } ) ;

We might achieve a similar outcome with the use of classes and inheritance. Also, major libraries and frameworks such as Angular and React adopted classes. Still, it might be worthwhile to get familiar with the above approach. Especially because it comes up often when looking into the “JavaScript Factory” term. The factory functions are not our primary focus in this article, though.

Factory method pattern

The Factory Method design pattern is one of the fundamental design patterns and is a part of the “Gang of Four” design patterns. It aims to provide a solution for creating objects without specifying the exact class of the object that is created.

Imagine creating a system for online coding teaching. One of your classes would probably be a teacher.

1 2 3 4 5 6 class Teacher { constructor ( name , programmingLanguage ) { this . name = name ; this . programmingLanguage = programmingLanguage ; } }

Looks fine at first glance, but the above approach might prove not to be scalable. If we decide to expand our business to teaching the music also, we might end up with something like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const TEACHER_TYPE = { CODING : 'coding' , MUSIC : 'music' } ; class Teacher { constructor ( type , name , instrument , programmingLanguage ) { this . name = name ; if ( type === TEACHER_TYPE . CODING ) { this . programmingLanguage = programmingLanguage ; } else if ( type === TEACHER_TYPE . MUSIC ) { this . instrument = instrument ; } } }

As you might see, the Teacher class is getting pretty nasty and would get even worse as we add more teacher types to the existing codebase. A common approach to the above issue is to implement the Factory Method pattern.

1 2 3 4 const TEACHER_TYPE = { CODING : 'coding' , MUSIC : 'music' } ;

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class CodingTeacher { constructor ( properties ) { this . name = properties . name ; this . programmingLanguage = properties . programmingLanguage ; } } class MusicTeacher { constructor ( properties ) { this . name = properties . name ; this . instrument = properties . instrument ; } } class TeacherFactory { static getTeacher ( type , properties ) { if ( type === TEACHER_TYPE . CODING ) { return new CodingTeacher ( properties ) ; } else if ( type === TEACHER_TYPE . MUSIC ) { return new MusicTeacher ( properties ) ; } } }

In the code above you can see that we can use the TeacherFactory to create any type of teacher. Also, we don’t have to worry about breaking any code when adding a new type.

1 2 3 4 5 6 7 8 9 const codingTeacher = TeacherFactory . getTeacher ( TEACHER_TYPE . CODING , { programmingLanguage : 'JavaScript' , name : 'John' } ) ; const musicTeacher = TeacherFactory . getTeacher ( TEACHER_TYPE . MUSIC , { instrument : 'Guitar' , name : 'Andy' } ) ;

An advantage of the Factory Method pattern that we might notice when we look at how we call the getTeacher function is that the type of an instance can be easily decided at the runtime.

We can also go a bit further and use inheritance to make our classes a bit cleaner.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Teacher { constructor ( properties ) { this . name = properties . name ; } } class CodingTeacher extends Teacher { constructor ( properties ) { super ( properties ) ; this . programmingLanguage = properties . programmingLanguage ; } } class MusicTeacher extends Teacher { constructor ( properties ) { super ( properties ) ; this . instrument = properties . instrument ; } }

Thanks to the above, we can delegate some of the common features into a separate class. To make our code more bug-proof, it might be a good idea to throw an error if a wrong type is chosen. Let’s use a switch statement!

1 2 3 4 5 6 7 8 9 10 11 12 class TeacherFactory { static getTeacher ( type , properties ) { switch ( type ) { case TEACHER_TYPE . CODING : return new CodingTeacher ( properties ) ; case TEACHER_TYPE . MUSIC : return new MusicTeacher ( properties ) ; default : throw new Error ( 'Wrong teacher type chosen' ) ; } } }

With our layer of abstraction, we don’t have to use the actual constructors at all. We can reuse our factory when our application keeps on growing without changing the previous types.

The Factory Method in TypeScript

The Factory Method pattern works very well with TypeScript if we do some neat type-checking.

1 2 3 4 enum TEACHER_TYPE { CODING = 'coding' , MUSIC = 'music' , }

1 2 3 4 5 6 7 8 9 interface TeacherProperties { name : string ; } class Teacher { public name: string ; constructor ( properties : TeacherProperties ) { this . name = properties . name ; } }

1 2 3 4 5 6 7 8 9 10 11 interface CodingTeacherProperties { name : string ; programmingLanguage : string ; } class CodingTeacher extends Teacher { public programmingLanguage: string ; constructor ( properties : CodingTeacherProperties ) { super ( properties ) ; this . programmingLanguage = properties . programmingLanguage ; } }

1 2 3 4 5 6 7 8 9 10 11 interface MusicTeacherProperties { name : string ; instrument : string ; } class MusicTeacher extends Teacher { public instrument: string ; constructor ( properties : MusicTeacherProperties ) { super ( properties ) ; this . instrument = properties . instrument ; } }

The catch to implementing the Factory in TypeScript is that we need to specify accurately which properties should go with a particular type of an object. To do so, we can use method overloading.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 class TeacherFactory { public static getTeacher ( type : TEACHER_TYPE . MUSIC , properties : MusicTeacherProperties ) : MusicTeacher ; public static getTeacher ( type : TEACHER_TYPE . CODING , properties : CodingTeacherProperties ) : CodingTeacher ; public static getTeacher ( type : TEACHER_TYPE , properties : MusicTeacherProperties & CodingTeacherProperties ) { switch ( type ) { case TEACHER_TYPE . CODING : return new CodingTeacher ( properties ) ; case TEACHER_TYPE . MUSIC : return new MusicTeacher ( properties ) ; default : throw new Error ( 'Wrong teacher type chosen' ) ; } } }

In the code above, we specify explicitly that when we create a music teacher, we need to pass the properties of a music teacher. Otherwise, the TypeScript compiler would throw an error.

1 2 3 4 5 6 7 8 9 const codingTeacher = TeacherFactory . getTeacher ( TEACHER_TYPE . CODING , { programmingLanguage : 'JavaScript' , name : 'John' , } ) ; const musicTeacher = TeacherFactory . getTeacher ( TEACHER_TYPE . MUSIC , { instrument : 'Guitar' , name : 'Andy' , } ) ;

Thanks to proper typing, TypeScript now knows that the codingTeacher, for example, is an instance of the CodingTeacher class.

1 console . log ( codingTeacher . instrument ) ;

error TS2339: Property ‘instrument’ does not exist on type ‘CodingTeacher’.

Summary

In this article, we’ve managed to shed some light on what a factory method pattern is and how it shouldn’t be confused with factory functions. To do the above, we’ve implemented both factory functions and the factory method pattern. We’ve also learned how to use the above pattern with TypeScript. While it might be beneficial and help to keep our code clean, it is essential not to overuse it. If we won’t profit from this level of complexity, or we don’t need to generate instances judging their types during runtime, we might introduce make our code unnecessary complicated. If we watch out for the above issues, the factory pattern might come in handy.