Singlenton en typescript
Tags: tutorial, typescript, javascript, webdev
Published: 2021-10-13 14:13:11

Una de las cosas que nos hacen buenos programadores es no reinventar la rueda es por eso que aprender patrones diseño los cuales son soluciones a ciertos problemas en el desarrollo de software es fundamental.

Tampoco creo que sea necesario  aprenderse la implementación todos los patrones de diseño y dominarlos a la perfección, pero aprender los conceptos de algunos nos podría dar una idea de cuál sería la solución para cierto problema que se nos presente en nuestro día a  día como programadores.

En esta serie de stories quiero ir compartiendo mi  progreso de aprendizaje de algunos patrones de diseño , La implementación de estos estarán en TypeScript porque es el lenguaje que más me gusta, pero la idea de los patrones de diseño es que son independientes del lenguaje, quizás existan algunas variaciones, pero después la lógica sería la misma.

## Singlenton

Este patrón es uno de los más facilita a implementar y también uno de los menos utilizados desde mi punto de vista, debido a que este viola el [principio de responsabilidad única de (SRP)](https://es.wikipedia.org/wiki/Principio_de_responsabilidad_%C3%BAnica). pero eso es muy útil a la hora de querer asegurar que solo sea instanciada una vez de esa manera se tiene una instancia global  y única. 

**Implementación:**

1.  Añade un campo estático del mismo tipo de la clase para almacenar la instancia.
2. Hacer que el  constructor sea privado  : De esta manera aseguramos que nuestra clase no pueda instanciarse desde el exterior.
3. Define método para retornar la instancia: Este método va a retornar la instancia de la clase, si esta existe retorno la instancia, si no, creo una, la retorno y la guardo porque ya se ha creado.

Para hacer esto mas representativo voy ha hacer una clase que nos sirve de "base de datos" .

primero definamos el comportamiento de nuestra base datos 

```tsx
interface BaseRecord {
  id: string;
}
// gracias a los genericos puedo tener el tipado correspondiente en este caso T es un tipo que debe de extender de base Record osea tener el id
interface IDatabase<T extends BaseRecord> {
  find(id: string): T;
  findAll(properties: PartialAssert<T>): T[];
  insert(node: T): void;
  delete(id: string): T;
}
```

Ahora con la  interface he definido las operaciones que debería de tener las clases que implementen esta para cumplir el rol de BD  en este caso solo son cuatro. Ahora voy a crear una base datos para mis todos.

```tsx

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

class TodosDatabase implements IDatabase<Todo> {
  nodes: Record<string, Todo> = {};
  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;
  }
}
```

Aquí lo único raro es el método `findAll` mi objetivo era poder buscar por propiedad por ejemplo para buscar todos los todos que estén tachados, solo haría lo siguiente:

```tsx
const todosCheked = todoDatabase.findAll({
  done: true
});
```

Para  eso implemente  un pequeño método que realiza esa lógica en este momento no es muy importante pero lo dejare como un extra.

```tsx

export const allPropsAreEmpty = (filters: { [key: string]: unknown }) => {
  return Object.values(filters).every((val: any) => {
    return typeof val == 'undefined';
  });
};
export type Paginate = { limit?: number; skip?: number };

export type PartialAssert<T> = {
  [P in keyof T]?: T[P] | ((val: T[P]) => boolean);
} & Paginate;

const handleSizeArr =
  <T extends unknown>(arr: T[]) =>
  (skip: number, limit: number) => {
    return arr.slice(skip, limit);
  };

export function assertProps<T>(arr: T[]) {
  return ({ limit, skip, ...props }: PartialAssert<T>) => {
    if (allPropsAreEmpty(props)) return arr;
    return handleSizeArr(arr)(skip, limit).filter((can: any) => {
      return Object.keys(props).every((d: any) => {
        const safeProps: any = props;
        if (typeof safeProps[d] == 'function') {
          return safeProps[d]((can as any)[d]);
        }
        return can[d] === safeProps[d];
      });
    });
  };
}
```

La verdad no sé que tan eficiente sea esto y pido disculpas pero quería lograr mi requerimiento antes planteado 😁. Bien hasta ahí nada fuera de lo normal, para usar esto podría instancia la clase y listo

```tsx
const todoDatabase = new TodosDatabase();

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

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

const todosCheked = todoDatabase.findAll({
  done: true
});
/*
[ { done: false, id: '1', priority: 2, title: 'Sleep early' } ]
*/
```

¿Pero qué pasaría si quisiera insertar todos desde otro lado?, ¿Creo otra nueva instancia?. La respuesta seria no porque tendría `todos` por clase y eso estarla muy mal, Bien aquí es donde el SIngleton viene al rescate vamos a hacer que nuestra clase solo sé instancia una vez y nos vamos a asegurar que no se pueda instancia desde el exterior solo desde la misma clase:

```tsx
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;
  }
}
```

Bueno recordar también que la participación de `static`  es esencial dado que esto nos permite utilizar la propiedad sin instanciar la clase. Pero ahora ya no podremos instancia la clase 🤐 porque el contructor es privado , por eso convenientemente creamos el método `instance` que es público y nos entrega la instancia.

```tsx

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({
  done: true
});

console.log(todosCheked);
/*

*/
```

Por cierto `findAll`  también puede ser usado así ;

```tsx
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
});

console.log(todosCheked);
/*
[ { done: true, id: '2', priority: 2, title: 'do the laudry' } ]
*/
```

### Referencias :

[https://refactoring.guru/es/design-patterns/singleton](https://refactoring.guru/es/design-patterns/singleton)