The Factory Pattern
The Factory Pattern is a versatile design pattern that simplifies object creation, by providing a method that acts as a “factory” for creating objects. It abstracts the instantiation process, allowing the type of object designed to be determined at runtime.
Image creating a user authentication system where users have different roles like Admin, Members or Guest. Each role might have a distinct authentication mechanism and permissions.
Before using the Factory Pattern:
type UserRole = 'Admin' | 'Member' | 'Guest';
type User = { role: UserRole; authenticate: () => string; };
const createAdminUser = (): User => ({ role: 'Admin', authenticate: () => 'Admin authenticated' });
const createMemberUser = (): User => ({ role: 'Member', authenticate: () => 'Member authenticated' });
const createGuestUser = (): User => ({ role: 'Guest', authenticate: () => 'Guest authenticated' });
let user: User;
const role: UserRole = 'Admin';
if(role === 'Admin') {
user = createAdminUser();
} else if(role === 'Member') {
user = createMemberUser();
} else if(role === 'Guest') {
user = createGuestUser();
} else {
throw new Error('Invalid user role');
}
After using the Factory Pattern:
type UserRole = 'Admin' | 'Member' | 'Guest';
type User = { role: UserRole; authenticate: () => string; };
const createAdminUser = (): User => ({ role: 'Admin', authenticate: () => 'Admin authenticated' });
const createMemberUser = (): User => ({ role: 'Member', authenticate: () => 'Member authenticated' });
const createGuestUser = (): User => ({ role: 'Guest', authenticate: () => 'Guest authenticated' });
const createUser = (role: UserRole): User => {
switch(role) {
case 'Admin': return createAdminUser();
case 'Member': return createMemberUser();
case 'Guest': return createGuestUser();
default: throw new Error('Invalid user role');
}
};
const user = createUser('Admin');
Key Points
Decoupling: Client code is decoupled from the object creation code, promoting modularity.
Flexibility: New types/functions can be added seamlessly without altering existing code.
Abstraction: The client code does not need to know about the concrete types or creation logic.
We can see that the Factory Pattern abstracts away the complexity and provides a cleaner, more maintainable and scalable solution. However, as with everything, its crucial to apply it only where the benefits outweigh the introduced abstraction layer.
When to use:
The Factory Pattern is highly beneficial in certain scenarios where object creation complexity needs to be managed and controlled effectively.
Diverse Object Creation: When your system needs to create objects from several classes or types, and the exact type isn’t known until runtime.
Complex Instantiation Logic: If the instantiation of an object involves multiple steps, conditions, or configurations that you want to encapsulate from the client code.
Decoupling Object Creation: When you want to decouple the object creation code from the code that uses the object, promoting modular design and enhancing code maintainability.
Flexibility in Object Creation: If your system is expected to be extended or modified in the future, a factory can provide a flexible and centralised way to manage these changes.
Managing Dependencies: When you need to manage, control, or track objects being created, such as maintaining a registry of created instances.
Switching Implementations: You may need to switch between different implementations or configurations of an object dynamically at runtime.
Examples:
Payment Gateway Integration: In e-commerce applications, where different payment gateways (like PayPal, Stripe, or a bank transfer) might be used, and the choice of gateway might depend on user preferences or regional settings.
UI Component Rendering: In UI libraries or frameworks, different components need to be rendered based on user interactions or data. A factory method could create and return the appropriate component dynamically.
Database Connections: In scenarios where an application might connect to different types of databases (SQL, NoSQL, local, remote) based on configurations or user data.
User Role Management: When various user roles need different authentication and authorization mechanisms, a user factory can create instances of different user classes (Admin, Member, Guest) based on the role data.