Patrón factory en typescript
Published: 2021-10-16 03:59:03

Continuando con esta serie de stories sobre patrones de diseño, el siguiente es Factory. Este es un patrón creacional y ofrece una de las mejores formas de crear un objeto.

Como su nombre lo dice es una fábrica y su función es fabricar algo en este caso un objeto, pero va más allá de eso. Según mi análisis, el patrón factory serviría cuando tenemos que crear algo y tenemos diferentes maneras de crear ese algo. Y además hay una lógica de por medio para su creación, Veamos un ejemplo;

Imagina que estás creando un bot para Facebook lo más básico seria tener lo siguiente.


```tsx
class Message {
  send() {
    // send message logic
  }
}
```

Hasta ahí todo fácil, su aplicación funciona, todo va de maravilla, pero después de un tiempo se da cuenta de que no solo se puede enviar texto, sino también botones, galerías y más.

Poner más y más lógica a la clase sería complicado. Así que decide crear una clase para cada tipo de mensaje

```tsx
class TextMessage {}

class ImageMessage {}

class ButtonMessage {}

class GaleryMessage {}
```

 Ahora  detecta que todos estos tipos tienen algo en común. Estos se tienen que enviar  el mensaje y de la misma manera quizás, así que decide abstraer toda esa lógica de los mensajes y heredarla.

```tsx
abstract class Message {
  id: string;
  send(): void {}
}

class TextMessage extends Message {
  id = 'Text';
}

class ImageMessage extends Message {
  id = 'Image';
}

class ButtonMessage extends Message {
  id = 'Button';
}

class GaleryMessage extends Message {
  id = 'Galery';
}
```

A mi parecer luce bien, pero ahora el problema es que complicamos un poco el código.

Bien seamos un poco amables con él, así que le vamos a brindar  una manera  fácil de crear su mensaje.

```tsx
function createMessage(type: TypeMessage) {
  switch (type) {
    case TypeMessage.TEXT:
      return new TextMessage();
    case TypeMessage.BUTTON:
      return new ButtonMessage();
    case TypeMessage.IMAGE:
      return new ImageMessage();
    case TypeMessage.GALERY:
      return new GaleryMessage();
  }
}

const btnMessage = createMessage(TypeMessage.BUTTON);
btnMessage.send();
const textMessage = createMessage(TypeMessage.TEXT);
textMessage.send();
```
El método `createMessage` sería nuestra fábrica y el producto que nos entrega es un `Message` esto es muy importante porque la fabrica no distingue si estamos trabajando con `ButtonMessage` o `TextMessage`  porque los trata por su clase abstracta en este caso  `Message.` 
Ahora regresemos al ejercicio del primer patrón  y agreguemos **factory** que por cierto era este:

```tsx
import { assertProps, PartialAssert } from './utils';
interface BaseRecord {
  id: string;
}
interface IDatabase<T extends BaseRecord> {
  find(id: string): T;
  findAll(properties: PartialAssert<T>): T[];
  insert(node: T): void;
  delete(id: string): T;
}

interface Todo extends BaseRecord {
  title: string;
  done: boolean;
  priority: number;
}

class TodosDatabase implements IDatabase<Todo> {
  nodes: Record<string, Todo> = {};
  // aqui podemos guardar la instancia
  private static _instance: TodosDatabase = null;

  // este método se encarga de exponer la instancia hacía el exterior
  public static get instance(): TodosDatabase {
    // si la instancia no existe es por que todavìa no ha sido creado
    if (TodosDatabase._instance == null) {
      TodosDatabase._instance = new TodosDatabase();
    }
    return TodosDatabase._instance;
  }
  private constructor() {}
  find(id: string): Todo {
    return this.nodes[id];
  }
  findAll(properties: PartialAssert<Todo>): Todo[] {
    const find = assertProps(Object.values(this.nodes));
    return find(properties);
  }
  insert(node: Todo): void {
    this.nodes[node.id] = node;
  }
  delete(id: string): Todo {
    const deleted = this.nodes[id];
    delete this.nodes[id];
    return deleted;
  }
}

const todoDatabase = TodosDatabase.instance;

TodosDatabase.instance.insert({
  done: false,
  id: '1',
  priority: 2,
  title: 'Sleep early'
});

TodosDatabase.instance.insert({
  done: true,
  id: '2',
  priority: 2,
  title: 'do the laudry'
});

const todosCheked = TodosDatabase.instance.findAll({
  title: (title: string) => {
    return title.indexOf('do') != -1;
  },
  done: true
});
```

Bien esto funciona perfecto, pero ahora viene un gran hombre y nos dice que desea un CRUD de productos 😒.

La solución más rápida de pensar, pero difícil y aburridamente de implementar sería volver a hacer el mismo procedimiento con producto, pero ya que hemos visto factory es hora de usarlo,

Lo primero que voy hacer es crear un función que me cree una clase Database.

```tsx
function createDatabase<T extends BaseRecord>() {
  class Database implements IDatabase<T> {
    nodes: Record<string, T> = {};
    private static _instance: Database = null;
    public static get instance(): Database {
      if (Database._instance == null) {
        Database._instance = new Database();
      }
      return Database._instance;
    }
    private constructor() {}

    find(id: string): T {
      return this.nodes[id];
    }
    findAll(properties: PartialAssert<T>): T[] {
      const find = assertProps(Object.values(this.nodes));
      return find(properties);
    }

    insert(node: T): void {
      this.nodes[node.id] = node;
    
    }
    delete(id: string): T {
      const deleted = this.nodes[id];
      delete this.nodes[id];
      return deleted;
    }
  }
  return Database;
}
```

Esto es casi igual solo que ahora el genérico parte desde la función hacia la clase y por consecuencia podemos hacer lo siguiente.

```tsx
const TodosDatabase = createDatabase<Todo>();
const ProductDatabase = createDatabase<Product>();
```

Bellísimo,  ahora ya tenemos dos `Database`  una para productos y otra para las tareas por hacer.

```tsx
TodosDatabase.instance.insert({
  done: false,
  priority: 2,
  title: 'Sleep early'
});

TodosDatabase.instance.insert({
  done: true,
  priority: 2,
  title: 'do the laudry'
});

ProductDatabase.instance.insert({
  name: 'Product 1',
  price: 5
});

TodosDatabase.instance.findAll({
  done: false
});
```

Y con todo el poder de typescript incluido, después de un tiempo otra vez vuelve nuestro amigo y nos dice que funciona perfecto, pero no siempre puede ingresar el `id` y que requiere que este sea automático, si hubieras aplicado factory tendrías que tocar dos clases, pero como no lo hiciste 😌.


```tsx
interface IConfig {
  typeId: 'uuid' | 'manual' | 'incremental';
}
function createDatabase<T extends BaseRecord>({ typeId }: IConfig) {
  class Database implements IDatabase<T> {
    nodes: Record<string, T> = {};

    private static _instance: Database = null;
    private _lastInserted: T;
   // ...extra logic
    private generateId() {
      switch (typeId) {
        case 'incremental': {
          if (!this._lastInserted) {
            return 0;
          } else {
            const id = this._lastInserted.id as number;
            return id + 1;
          }
        }
        case 'uuid': {
          return nanoid();
        }
        case 'manual': {
          return null;
        }
      }
    }

    insert(node: T): void {
      const id = this.generateId();
      if (id !== null) {
        node = {
          ...node,
          id
        };
      }

      this.nodes[node.id] = node;
      this._lastInserted = node;
    }
    // ...extra logic
  }
  return Database;
}
```

Agregamos un parámetro para las configuraciones, por si a nuestro amigo se le ocurre pedir más cosas. Ahora hacemos magia, tenemos tres forma de producir un id:

- Manual : EL usuario ingresa el id.
- Incremental : Un número que aumenta de uno en uno.
- Uuid : Una identificador único generado por el paquete [nanoid](https://www.npmjs.com/package/nanoid).

Toda esa lógica lo maneja el método `generateId` por cierto hubo un cambio en la siguiente interfaz.

```tsx
interface BaseRecord {
  id?: string | number;
}
```
Ahora es opcional y nuestro, id puede ser número o una cadena. La propiedad `_lastInserted` cumple el rol de ir guardando el último elemento insertado porque me parece mucho más barato que recorrer todos los datos buscando el mayor o el último insertado.


Puedes ver el código completo [aquí](https://stackblitz.com/edit/typescript-lekhux?file=factory/2.ts)