Factory pattern - Type script implementation with type map
A practical example of implementing a factory pattern that takes advantage of the TypeScript Typing System
Factory pattern is one of the most common creation patterns. It loosely connects the application by hiding the implementation details from the client code using the interface. It leaves the creation of object instances to the factory implementation.
This article aims to show how to improve the factory “creator” with a TypeScript type map.
Users Factory Implementation
We’ll be using a typical example of a factory that creates different types of users: Manager and Developer, for the sake of simplicity. You can imagine there will be many more types in a real project.
Firstly, let’s define our Manager and Developer classes.
In the above code, we define an IStaff
interface, so that both Manager and Developer classes implement it. It’s essential for the Factory pattern to work, so the client code doesn’t need to know a particular class, only the interface.
Another notable thing is the use of the “NullStaff
” class (Null Object Pattern ). It simplifies the client code and avoids run time bugs when an invalid user type is passed in.
Finally, we have the UserFactory implemented below and the UserService
as the client that consumes the factory. The UserService
does not know the Manager or Developer class. It just passes in a userType
and gets a concrete instance of the user class back.
Things to improve
There are a couple of things that I am not entirely satisfied with.
Firstly, the Switch case is used in the static factory creator function. This is a code smell, as it will keep growing as more user types are added.
Secondly, the user
type is a string type. Thus, the caller can pass in an invalid user type string. We use the Null object pattern to prevent the run time error, but why do we allow it to happen in the first place?
Type map
Using a Typescript type map, we can improve the factor implementation to make it more generic and resolve the two issues above.
Firstly, let’s separate the type mapping into a type map Constant.
const userMap = {
dev: Developer,
manager: Manager
};
The “Keys
” represents a union of literal types for all user types.
type Keys = keyof typeof userMap; // ‘dev’ | ‘manager’
Then, we use the typeof
operator to get the union types of all user classes.
type userTypes = typeof userMap[Keys]; //typeof Developer | typeof Manager
Infer the Class
We want to get the return type of user class T
. But the userTypes
type contains all classes as typeof Developer| typeof Manager
which is equal to new() => Developer| new() => Manager
. To get the user Class T, we use TypeScript Infer as below.
type ExtractInstanceType
So now we can implement a simpler and cleaner factory.
Benefits
The improved factory implementation removes the Switch-case statement and accepts the “Keys” literal types. Thus, when a new user type is added, it only needs to be added to “userMap
” Constant.
If an incorrect user type is passed into the UserService, TypeScript will detect the issue and complain as below.
Conclusion
Combining the Type script typing with mapped type and conditional type operators, we improve the factor pattern implementation to make it more generic and add stronger type support.
The above implementation can also be further enhanced to support passing in constructor parameters. I’ll leave it to you as an exercise. :)
The sample source code can be found in this stackblitz link.
Update (An enhanced version)
Thanks for the contribution from [Konstantin Kovalev]
The gist of the solution is shown below. You can find the code here .
The solution uses InstanceType to create a tuples union type, then use the Extract Utility type to map a key to the corresponding instance type.
It is just another showcase of TypeScript Magics!
- Title: Factory pattern - Type script implementation with type map
- Author: Sunny Sun
- Created at : 2021-04-04 00:00:00
- Updated at : 2024-08-16 19:46:17
- Link: http://coffeethinkcode.com/2021/04/04/factory-pattern-in-typescirpt-using-typemap/
- License: This work is licensed under CC BY-NC-SA 4.0.