Como usar el Patrón Factory en Javacript
El patrón Factory es uno de esos patrones que son increíblemente útiles y sencillos de implementar, ayudando a escribir un código más mantenible y comprensible. Profundicemos en este patrón con un ejemplo simple.
El asistente personal (o patrones creacionales)
Me gusta pensar en el patrón Factory como una especie de asistente que nos permite crear objetos fácilmente. Estrictamente hablando, este patrón pertenece a la categoría de patrones creacionales, que nos ayudan a crear otros objetos para hacer nuestro código más flexible y reutilizable.
Si bien existen otros patrones creacionales como Abstract Factory, Builder, Prototype o el a menudo criticado Singleton, hoy nos centraremos en el Factory Method.
Un sistema de notificaciones
Imagina que tienes una aplicación donde una de sus partes se encarga de enviar notificaciones. Inicialmente, solo envías correos electrónicos, pero en versiones posteriores, comienzas a incluir la capacidad de enviar SMS, notificaciones PUSH, notificaciones vía Discord o Slack, y así sucesivamente.
A medida que incluimos más tipos de notificaciones, el código comienza a complicarse, llenándose de condicionales dependiendo del tipo de notificación que queramos enviar. En última instancia, nuestro código pierde calidad.
Para evitar que nuestro código se vuelva desordenado, podemos usar un patrón creacional, en este caso, el patrón Factory, para crear objetos de notificación, siempre utilizando los mismos métodos independientemente del tipo de notificación que queramos ejecutar.
Caso de uso: Sistema de notificaciones
Inicialmente, crearemos nuestra clase Notification. Esta clase solo tendrá su constructor donde pasamos content (que será el tipo de notificación) y el método send, que no implementaremos directamente, sino en cada tipo de notificación.
class Notification {
constructor(content) {
this.content = content;
}
send() {
throw new Error('Send method not implemented');
}
}
A continuación, crearemos los tipos de notificación (email, SMS y PUSH), y estas clases heredarán de la clase principal Notification, asegurando que siempre tengamos el método send. Este método aplicará la lógica necesaria para enviar el tipo específico de mensaje.
class EmailNotification extends Notification {
constructor(recipient, content) {
super(content);
this.recipient = recipient;
}
send() {
console.log(`Sending email to ${this.recipient}: ${this.content}`);
}
}
class TextMessageNotification extends Notification {
constructor(number, content) {
super(content);
this.number = number;
}
send() {
console.log(`Sending text message to ${this.number}: ${this.content}`);
}
}
class PushNotification extends Notification {
constructor(device, content) {
super(content);
this.device = device;
}
send() {
console.log(`Sending push notification to ${this.device}: ${this.content}`);
}
}
Ahora crearemos nuestro factory que implementará los distintos tipos.
class NotificationFactory {
constructor() {
this.types = {};
}
registerType(type, classRef) {
if (!(classRef.prototype instanceof Notification)) {
throw new Error('Class must be a subclass of Notification');
}
this.types[type] = classRef;
}
createNotification(type, ...args) {
const NotificationClass = this.types[type];
if (!NotificationClass) {
throw new Error('Notification type not registered');
}
return new NotificationClass(...args);
}
}
Analicemos un poco la clase.
El método registerType se encargará de registrar el tipo de notificación. Primero, se asegura de que el tipo de notificación sea del tipo Notification (con lenguajes tipados como TypeScript podríamos omitir esta comprobación). Luego, agrega el tipo a una propiedad de la clase llamada types, agregando una clave que será el nombre del tipo y su valor, que será el objeto en sí (EmailNotification, TextMessageNotification o PushNotification).
El método createNotification recibe el tipo de notificación (type) y una serie de argumentos que pasaremos directamente a la clase. Previamente, buscamos dentro de la propiedad types si existe el tipo de notificación, lanzando una excepción si no existe, o de lo contrario, devolviendo una instancia del tipo de notificación.
Por último, solo nos queda usar el patrón.
const factory = new NotificationFactory();
factory.registerType('email', EmailNotification);
factory.registerType('text', TextMessageNotification);
factory.registerType('push', PushNotification);
const email = factory.createNotification('email', 'user@example.com', 'Hello!');
const text = factory.createNotification('text', '5551234567', 'Hello!');
const push = factory.createNotification('push', 'device123', 'Hello!');
email.send(); // Output: Sending email to user@example.com: Hello!
text.send(); // Output: Sending text message to 5551234567: Hello!
push.send(); // Output: Sending push notification to device123: Hello!
Como puedes ver, si quisiéramos crea un nuevo tipo de notificación, simplemente tendríamos que registrar un nuevo tipo que cumpliese con la norma de ser una clase que herede de notification y llamar al método send()
El patrón Factory nos proporciona una forma elegante de crear objetos sin acoplar el código a clases concretas, lo que lo hace ideal para situaciones donde la lógica de creación de objetos puede cambiar o expandirse con el tiempo. Implementar este patrón nos permite mantener nuestro código limpio, modular y fácil de mantener.