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). 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:
- Añade un campo estático del mismo tipo de la clase para almacenar la instancia.
- Hacer que el constructor sea privado : De esta manera aseguramos que nuestra clase no pueda instanciarse desde el exterior.
- 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
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.
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:
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.
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
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:
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.
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í ;
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' } ]
*/
Top comments (0)