Factory pattern is a well known creator pattern. Problem with factory pattern is whenever there is a new concrete class that will be instantiate by the creator class (that have factory method), you have to add new ‘case’ in a switch or if / else.
So this violate the Open Close Principle which states open for extensions but close for modifications.
Code below using typescript, but the concept is same for other programming language.
Factory Pattern with Switch or If Else
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public static create(shape: string): Shape{ switch(type){ case 'circle': return new Circle(); case 'rectangle': return new Rectangle(); case 'triangle': return new Triangle() default: throw Error(`Shape is not supported`); } } |
If you want to add Square shape, we need to add another case in the factory.
1 2 |
case 'square': return new Square() |
The concept is the same with If Else
So I need to open my factory class every time there is a new shape to be add in.
Factory Pattern without Switch & If / Else
There are 3 main classes
1) Factory class
– it will import the shape class files in the folder /shapes
– instantiate the object based on shape type
2) Shape abstract class
– abstract method of getType()
3) Shape concrete class
– implement the getType() to tell what type is the shape e.g round
– located in /shapes folder
The concrete class Shape will implement a function to tell it is what kind of Shape.
1 2 3 4 5 6 7 8 9 |
class abstract Shape{ public abstract getType(): string; } class Round extends Shape{ public getType(): string{ return 'round' } } |
The factory class will import all the shape file path and instantiate the object when call by user class.
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
import * as fs from 'fs'; import * as path from 'path'; /** Class representing a FieldFactory. */ export default class ShapeFactory{ private static instance: ShapeFactory; private fieldTypeClass: Map<string, string>; constructor(){ this.fieldTypeClass = new Map<string, string>(); } //i use singleton to load once the file paths public static async getInstance(): Promise<ShapeFactory>{ if (!this.instance) { this.instance = new ShapeFactory(); await this.instance._loadFieldPaths(); } return this.instance; } public async create(type: string): Promise<Shape>{ if(!this.fieldTypeClass.has(type)){ throw Error(`Shape is not supported.`) } const filenameFilePath = this.fieldTypeClass.get(type); const main = await import(filenameFilePath); //instantiate new shape return new main.default(); } protected async _loadFieldPaths(){ const directoryPath = path.join(__dirname, 'fields'); const fileNames = fs.readdirSync(directoryPath); for(const fileName of fileNames){ const main = await import(directoryPath + '/' + fileName); const app = new main.default(); // console.log('type ', app.getType()) this.fieldTypeClass.set(app.getType(), directoryPath + '/' + fileName); } } }//class |
How To Use It?
1 |
const round = await ShapeFactory.getInstance().create('round') |
Impact
It requires async / await because needs to load dynamically the shape class.
This factory pattern can’t be used in a constructor.