El patrón Builder en Angular

Angular logo

El patrón Builder (perteneciente a la familia de patrones creacionales) nos permite crear un objeto con múltiples opciones de configuración mediante un conjunto de llamadas secuenciales. Antes que nada, es recomendado leer el artículo “Creación de objetos en typescript”.

Patrón Builder

Iniciaremos nuestra andadura creando una interfaz que cualquier builder deberá implementar. Esta interfaz contiene un método “build()” que devolverá el objeto resultante construido que deberá llamarse al finalizar la cadena de llamadas secuenciales.

No tiene que ser obligatoriamente ese nombre “build”, si lo crees conveniente puede llamarse también “create” por ejemplo.

export interface IBuilder<T> {
    build(): T;
}

Para ayudar en el desarrollo del builder nos apoyaremos en una clase abstracta para contener parte común de la lógica que cualquier builder que implementemos debería tener:

export abstract class BuilderTemplate<T> implements IBuilder<T>{
 
    protected _model: T;
 
    public constructor() {
        this._model = this.initialize();
    }
 
    public build(): T {
        return this._model;
    }
 
    protected abstract initialize(): T;
}

Simplemente tiene la declaración de una propiedad protected que contiene el objeto que se va construyendo junto con el método build que devuelve dicho objeto.

Obliga también a crear un método “initialize()” el cual inicializa el objeto con unos valores por defecto.

Antes de crear la clase, debemos saber qué tipo de objeto vamos a crear, así que traeremos la interfaz que ha de cumplir el objeto a crear. En nuestro caso, una objeto muy sencillo:

interface IIdentity<T> {
    id?: T
}
 
export interface IInvoice extends IIdentity<number> {
    totalAmount: number;
    invoiceDate: Date;
    invoiceNumber: string;
}

Finalmente el builder construirá un objeto tipo «IInvoice»:

export class InvoiceBuilder extends BuilderTemplate<IInvoice> {
 
    protected initialize(): IInvoice {
        return {
            invoiceDate: new Date(),
            invoiceNumber: '',
            totalAmount: 0
        };
    }
 
    public invoiceDate(invoiceDate: Date): InvoiceBuilder {
        this._model.invoiceDate = invoiceDate;
        return this;
    }
 
    public invoiceNumber(invoiceNumber: string): InvoiceBuilder {
        this._model.invoiceNumber = invoiceNumber;
        return this;
    }
 
    public totalAmount(totalAmount: number): InvoiceBuilder {
        this._model.totalAmount = totalAmount;
        return this;
    }
}

Como podemos observar creamos el método “initialize()” el cual crea un objeto con valores iniciales mediante un objeto literal (ver “Creación de objetos en typescript”).

Posteriormente añadimos los métodos necesarios para ir asignando cada propiedad del objeto y devuelve el «this» para poder ir encadenando diferentes llamadas.

Uso

Finalmente su uso sería de la siguiente manera:

const invoiceDefault: IInvoice = new InvoiceBuilder().build();
const invoice: IInvoice = new InvoiceBuilder().invoiceNumber('my invoice').totalAmount(10).build();

La primera construcción nos devolverá un objeto «IInvoice» con valores por defecto puesto que no estamos asignando ningún valor a sus propiedades.

La segunda construcción nos devolverá un objeto «IInvoice» con un «invoiceNumber» y «totalAmount» diferente a los de por defecto donde las llamadas se hacen una tras otra.

En ambas, creamos la nueva instancia del builder new InvoiceBuilder()”, y acaba con la llamada “build()”. 

Director

A partir de aquí puedes usar el builder asignándole los diferentes valores que desees. No obstante, se puede crear una clase llamada “Director” al cual se le inyectará el builder y podrá tener diferentes configuraciones de facturas. Por ejemplo podemos tener un Director para crear facturas de forma aleatoria o facturas con un importe determinado, etc.

Espero que pueda ser útil utilizar este patrón en los diferentes desarrollos. ¿Qué te ha parecido?

Puedes descargar el código del artículo en GitHub

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *