Este repositorio contiene implementaciones de diferentes patrones de diseño en TypeScript, con ejemplos prácticos y explicaciones detalladas.
- Factory Method (POO)
- Factory Method (Funcional)
- Strategy (POO)
- Strategy (Funcional)
- Observer (POO)
- Observer (Funcional)
- Adapter (POO)
El patrón Factory Method es un patrón creacional que proporciona una interfaz para crear objetos en una superclase, pero permite a las subclases alterar el tipo de objetos que se crearán.
Imagina que tienes un juego RPG donde necesitas crear diferentes tipos de personajes (Guerrero, Mago, PĂcaro, Sanador). El patrĂłn Factory Method te permite crear diferentes tipos de personajes sin modificar el cĂłdigo existente, delegando la creaciĂłn especĂfica a las subclases.
Creator (CharacterCreator)
├── character: Character (protected)
├── factoryMethod(): Character
├── attack(): void
└── greet(): void
ConcreteCreator (WarriorCreator, MageCreator, RogueCreator, HealerCreator)
└── factoryMethod(): ConcreteCharacter
Product (Character)
├── class: string
├── attack(): void
└── greet(): void
ConcreteProduct (Warrior, Mage, Rogue, Healer)
├── class: string
├── attack(): void
└── greet(): void
export interface Character {
class: string;
attack: () => void;
greet: () => void;
}import { Character } from "../product/Character";
abstract class CharacterCreator {
protected character: Character;
constructor() {
// 🏠Crea el personaje al instanciar el creator
this.character = this.factoryMethod();
}
// 🏠Factory Method - delega la creación a las subclases
abstract factoryMethod(): Character;
// ⚔️ Método que usa el personaje creado para atacar
attack(): void {
this.character.attack();
}
// 👋 Método que usa el personaje creado para saludar
greet(): void {
this.character.greet();
}
}WarriorCreator:
class WarriorCreator extends CharacterCreator {
factoryMethod(): Character {
return new Warrior();
}
}MageCreator:
class MageCreator extends CharacterCreator {
factoryMethod(): Character {
return new Mage();
}
}Warrior:
class Warrior implements Character {
class = "Warrior";
attack(): void {
console.log(`Warrior attacks with a sword!`);
}
greet(): void {
console.log(`Warrior says: For honor!`);
}
}Mage:
class Mage implements Character {
class = "Mage";
attack(): void {
console.log(`Mage casts a fireball!`);
}
greet(): void {
console.log(`Mage says: Knowledge is power!`);
}
}function main() {
const warrior = new WarriorCreator();
const healer = new HealerCreator();
const rogue = new RogueCreator();
const mage = new MageCreator();
warrior.attack();
healer.attack();
rogue.attack();
mage.attack();
warrior.greet();
healer.greet();
rogue.greet();
mage.greet();
}- Principio Abierto/Cerrado: Puedes agregar nuevos tipos de personajes sin modificar cĂłdigo existente
- Separación de responsabilidades: La lógica de creación está separada del uso
- Flexibilidad: Fácil intercambio de personajes
- Mantenibilidad: Código más organizado y fácil de mantener
- Complejidad: Puede hacer el código más complejo para casos simples
- JerarquĂa de clases: Requiere crear muchas subclases
- Cuando no sabes de antemano los tipos exactos de personajes que necesitarás
- Cuando quieres proporcionar a los usuarios una forma de extender el sistema de personajes
- Cuando quieres reutilizar personajes existentes en lugar de reconstruirlos
cd 01-factory-method/POO
npx ts-node index.tsSalida esperada:
Warrior attacks with a sword!
Warrior says: For honor!
Mage casts a fireball!
Mage says: Knowledge is power!
Rogue strikes from the shadows!
Rogue says: Silence is golden!
Healer casts a healing spell!
Healer says: Healing is my duty!
ImplementaciĂłn funcional del patrĂłn Factory Method aplicado a la creaciĂłn de personajes de juego. Esta versiĂłn demuestra cĂłmo implementar el patrĂłn usando funciones puras y composiciĂłn en lugar de clases e herencia.
En esta implementaciĂłn, creamos diferentes tipos de personajes (Guerrero, Mago, PĂcaro, Sanador) para un juego RPG, donde cada personaje tiene habilidades Ăşnicas de ataque y saludo.
Product (Character Type)
├── class: string
├── attack: () => void
└── greet: () => void
Factory Functions
├── createWarrior(): Character
├── createMage(): Character
├── createRogue(): Character
└── createHealer(): Character
Creator Function
└── createCharacter(type: string): Character
export type Character = {
class: string;
attack: () => void;
greet: () => void;
};function createWarrior(): Character {
return {
class: "Warrior",
attack: () => console.log("Warrior attacks with a sword!"),
greet: () => console.log("Warrior says: For honor!"),
};
}
function createMage(): Character {
return {
class: "Mage",
attack: () => console.log("Mage casts a fireball!"),
greet: () => console.log("Mage says: Knowledge is power!"),
};
}const factories: Record<string, () => Character> = {
warrior: createWarrior,
mage: createMage,
healer: createHealer,
rogue: createRogue,
};
function createCharacter(type: string): Character {
if (!factories[type]) throw new Error(`Unknown character type: ${type}`);
return factories[type]();
}function main() {
const warrior = createCharacter("warrior");
const mage = createCharacter("mage");
const healer = createCharacter("healer");
warrior.attack(); // Warrior attacks with a sword!
mage.attack(); // Mage casts a fireball!
healer.attack(); // Healer casts a healing spell!
warrior.greet(); // Warrior says: For honor!
mage.greet(); // Mage says: Knowledge is power!
healer.greet(); // Healer says: Healing is my duty!
}| Aspecto | POO | Funcional |
|---|---|---|
| AbstracciĂłn | Clases abstractas e interfaces | Types e interfaces |
| Creación | Métodos en clases heredadas | Funciones puras |
| Estado | Propiedades de instancia | Closures o parámetros |
| Extensibilidad | Herencia de clases | ComposiciĂłn de funciones |
| Complejidad | Mayor jerarquĂa de clases | Menos boilerplate |
| Testeo | Mocks y stubs de clases | Funciones más fáciles de testear |
| Memoria | Instancias de objetos | Funciones y closures |
- Simplicidad: Menos cĂłdigo boilerplate
- Pureza: Funciones puras sin efectos secundarios
- Composición: Fácil combinación y reutilización de funciones
- Testeo: Funciones más fáciles de testear de forma aislada
- Inmutabilidad: Los objetos creados son inmutables por diseño
- Falta de estado compartido: Cada personaje es independiente
- Menos expresivo: No aprovecha el polimorfismo orientado a objetos
- Escalabilidad: Puede ser menos organizado en aplicaciones grandes
cd 01-factory-method/functional
npx ts-node index.tsSalida esperada:
Warrior attacks with a sword!
Mage casts a fireball!
Healer casts a healing spell!
Warrior says: For honor!
Mage says: Knowledge is power!
Healer says: Healing is my duty!
El patrĂłn Strategy permite definir una familia de algoritmos, encapsular cada uno y hacerlos intercambiables. Permite que el algoritmo varĂe independientemente de los clientes que lo utilizan.
Estructura:
Strategy: Interfaz que define el métodoattack()- Estrategias concretas:
SwordStrategy,SpellStrategy,BowStrategy - Contexto:
CharacterAttackque usa una estrategia y permite cambiarla
Ejemplo real:
02-strategy/POO/strategy.ts
export interface Strategy {
attack(): void;
}02-strategy/POO/strategies/SwordStrategy.ts
import { Strategy } from "../strategy";
class SwordStrategy implements Strategy {
attack(): string {
return "This is a sword attack";
}
}
export default SwordStrategy;02-strategy/POO/strategies/SpellStrategy.ts
import { Strategy } from "../strategy";
class SpellStrategy implements Strategy {
attack(): string {
return "This is a spell attack";
}
}
export default SpellStrategy;02-strategy/POO/context.ts
import { Strategy } from "./strategy";
class CharacterAttack {
constructor(private strategy: Strategy) {
this.strategy = strategy;
}
setStrategy(strategy: Strategy) {
this.strategy = strategy;
}
attack(): void {
console.log(this.strategy.attack());
}
}
export default CharacterAttack;02-strategy/POO/index.ts
import CharacterAttack from "../POO/context";
import SwordStrategy from "../POO/strategies/SwordStrategy";
import SpellStrategy from "../POO/strategies/SpellStrategy";
const characterAttack = new CharacterAttack(new SwordStrategy());
characterAttack.attack();
characterAttack.setStrategy(new SpellStrategy());
characterAttack.attack();Salida esperada:
This is a sword attack
This is a spell attack
ImplementaciĂłn funcional del patrĂłn Strategy usando funciones en vez de clases.
Estructura:
Strategy: Tipo funciĂłn que representa una estrategia de ataque- Estrategias concretas:
swordStrategy,spellStrategy,bowStrategy - Contexto: funciĂłn
characterAttackque recibe una estrategia y la ejecuta
Ejemplo real:
02-strategy/functional/strategy.ts
export type Strategy = () => void;02-strategy/functional/strategies.ts
export function swordStrategy() {
return "This is a sword attack";
}
export function bowStrategy() {
return "This is a bow attack";
}
export function spellStrategy() {
return "This is a spell attack";
}02-strategy/functional/context.ts
import { Strategy } from "./strategy";
export function characterAttack(strategy: Strategy) {
return console.log(strategy());
}02-strategy/functional/index.ts
import { characterAttack } from "./context";
import { swordStrategy, spellStrategy } from "./strategies";
characterAttack(swordStrategy);
characterAttack(spellStrategy);Salida esperada:
This is a sword attack
This is a spell attack
El patrón Observer permite definir una dependencia uno-a-muchos entre objetos, de modo que cuando uno cambie su estado, todos sus dependientes sean notificados automáticamente. Es ideal para sistemas de eventos y notificaciones.
Desacoplar el emisor de eventos (Subject) de los receptores (Observers), permitiendo que los observadores reaccionen a cambios o eventos sin que el sujeto conozca sus detalles.
Subject (Dragon)
├── observers: Observer[]
├── attach(observer: Observer): void
├── detach(observer: Observer): void
└── notify(event: string): void
Observer (Mage, Warrior, Archer, Priest)
└── update(event: string): void
interface Observer {
update(event: string): void;
}interface Subject {
attach(observer: Observer): void;
detach(observer: Observer): void;
notify(event: string): void;
}class Dragon implements Subject {
private observers: Observer[] = [];
attach(observer: Observer): void {
this.observers.push(observer);
}
detach(observer: Observer): void {
this.observers = this.observers.filter((obs) => obs !== observer);
}
notify(event: string): void {
console.log(`Dragon: Notifying observers about event: ${event}`);
for (const observer of this.observers) {
observer.update(event);
}
}
}class Mage implements Observer {
update(event: string): void {
console.log(`Mage: Received event - ${event}. Preparing spells!`);
}
}
class Warrior implements Observer {
update(event: string): void {
console.log(`Warrior: Received event - ${event}. Ready for battle!`);
}
}
class Archer implements Observer {
update(event: string): void {
console.log(`Archer: Received event - ${event}. Ready to shoot arrows!`);
}
}
class Priest implements Observer {
update(event: string): void {
console.log(`Priest: Received event - ${event}. Ready to heal!`);
}
}const dragon = new Dragon();
const mage = new Mage();
const warrior = new Warrior();
const archer = new Archer();
const priest = new Priest();
dragon.attach(mage);
dragon.attach(warrior);
dragon.attach(archer);
dragon.attach(priest);
dragon.notify("The dragon has appeared!");
dragon.detach(archer);
dragon.notify("The dragon is attacking!");Salida esperada:
Dragon: Notifying observers about event: The dragon has appeared!
Mage: Received event - The dragon has appeared!. Preparing spells!
Warrior: Received event - The dragon has appeared!. Ready for battle!
Archer: Received event - The dragon has appeared!. Ready to shoot arrows!
Priest: Received event - The dragon has appeared!. Ready to heal!
Dragon: Notifying observers about event: The dragon is attacking!
Mage: Received event - The dragon is attacking!. Preparing spells!
Warrior: Received event - The dragon is attacking!. Ready for battle!
Priest: Received event - The dragon is attacking!. Ready to heal!
- Desacopla el emisor de los receptores
- Permite agregar/quitar observadores dinámicamente
- Facilita la extensiĂłn y reutilizaciĂłn
- Puede generar dependencias circulares
- DifĂcil de depurar en sistemas grandes
cd 03-observer/POO
npx ts-node index.tsImplementaciĂłn funcional del patrĂłn Observer usando funciones y composiciĂłn, ideal para sistemas reactivos y de eventos en JavaScript/TypeScript.
Permitir que mĂşltiples funciones (observadores) reaccionen a eventos emitidos por un sujeto, sin acoplamiento entre ellos.
Subject (createSubject)
├── observers: Observer[]
├── attach(observer: Observer): void
├── detach(observer: Observer): void
└── notify(event: string): void
Observer (mage, warrior, archer, priest)
└── (event: string) => void
export type Observer = (event: string) => void;export interface Subject {
attach(observer: (event: string) => void): void;
detach(observer: (event: string) => void): void;
notify(event: string): void;
}export const createSubject = (): Subject => {
let observers: Observer[] = [];
function attach(observer: Observer): void {
observers.push(observer);
}
function detach(observer: Observer): void {
observers = observers.filter((obs) => obs !== observer);
}
function notify(event: string): void {
for (const observer of observers) {
observer(event);
}
}
return { attach, detach, notify };
};export const mage: Observer = (event: string) => {
console.log(`Mage: Received event - ${event}. Preparing spells!`);
};
export const warrior: Observer = (event: string) => {
console.log(`Warrior: Received event - ${event}. Ready for battle!`);
};
export const archer: Observer = (event: string) => {
console.log(`Archer: Received event - ${event}. Ready to shoot arrows!`);
};
export const priest: Observer = (event: string) => {
console.log(`Priest: Received event - ${event}. Healing allies!`);
};const dragon = createSubject();
dragon.attach(mage);
dragon.attach(warrior);
dragon.attach(archer);
dragon.attach(priest);
dragon.notify("The dragon has appeared!");
dragon.detach(archer);
dragon.notify("The dragon is attacking!");Salida esperada:
Mage: Received event - The dragon has appeared!. Preparing spells!
Warrior: Received event - The dragon has appeared!. Ready for battle!
Archer: Received event - The dragon has appeared!. Ready to shoot arrows!
Priest: Received event - The dragon has appeared!. Healing allies!
Mage: Received event - The dragon is attacking!. Preparing spells!
Warrior: Received event - The dragon is attacking!. Ready for battle!
Priest: Received event - The dragon is attacking!. Healing allies!
- Sencillez y bajo acoplamiento
- Fácil de testear y extender
- ComposiciĂłn funcional
- Menos expresivo para sistemas complejos
- No hay polimorfismo clásico
cd 03-observer/functional
npx ts-node index.tsEl patrĂłn Adapter permite que dos interfaces incompatibles colaboren, actuando como un traductor entre ellas. Es Ăştil cuando quieres reutilizar cĂłdigo existente que no encaja con la interfaz que espera tu sistema.
Adaptar la interfaz de una clase existente (Creature) para que pueda ser utilizada como si implementara otra interfaz (Character).
SupĂłn que tienes una clase Dragon que implementa la interfaz Creature, pero tu sistema espera objetos que implementen la interfaz Character. El Adapter permite envolver el Dragon y usarlo como si fuera un Character, sin modificar la clase original.
Character (interface)
├── attack(): void
├── takeDamage(amount: number): void
└── showStatus(): number
Creature (interface)
├── strike(): void
├── receiveDamage(amount: number): void
└── getState(): number
CreatureAdapter (implements Character)
└── creature: Creature
├── attack() → strike()
├── takeDamage() → receiveDamage()
└── showStatus() → getState()
// interfaces/Character.ts
export interface Character {
attack: () => void;
takeDamage: (amount: number) => void;
showStatus: () => number;
}
// interfaces/Creature.ts
export interface Creature {
strike: () => void;
receiveDamage: (amount: number) => void;
getState(): number;
}// creatures/Dragon.ts
import { Creature } from "../interfaces/Creature";
export class Dragon implements Creature {
private health = 1000;
strike() {
console.log(`The dragon exales a fire breath!`);
}
receiveDamage(damage: number) {
console.log(`The dragon received ${damage} points of damage`);
this.health -= damage;
}
getState() {
console.log(`Dragon HP: ${this.health}`);
return this.health;
}
}// adapters/creature.adapter.ts
import type { Character } from "../interfaces/Character";
import type { Creature } from "../interfaces/Creature";
export class CreatureAdapter implements Character {
constructor(private creature: Creature) {}
attack() {
this.creature.strike();
}
takeDamage(amount: number) {
return this.creature.receiveDamage(amount);
}
showStatus() {
return this.creature.getState();
}
}// index.ts
import { Mage } from "./characters/Mage";
import { Dragon } from "./creatures/Dragon";
import { CreatureAdapter } from "./adapters/creature.adapter";
import { Character } from "./interfaces/Character";
const mage = new Mage();
const dragon = new Dragon();
const adapteeDragon = new CreatureAdapter(dragon);
function activateCharacter(character: Character) {
character.attack();
character.takeDamage(15);
character.showStatus();
}
activateCharacter(adapteeDragon);
activateCharacter(mage);cd 04-adapter/POO
npx ts-node index.tsSalida esperada:
The dragon exales a fire breath!
The dragon received 15 points of damage
Dragon HP: 985
The mage throw a fireball!
The mage has received 15 points of damage!
Mage HP: 85
- Permite reutilizar cĂłdigo existente sin modificarlo
- Desacopla el cliente de la implementaciĂłn concreta
- Facilita la integraciĂłn de sistemas con interfaces incompatibles
- Puede añadir una capa extra de complejidad
- Si hay muchas adaptaciones, puede dificultar el mantenimiento
- Cuando necesitas usar una clase existente pero su interfaz no es compatible
- Cuando quieres integrar sistemas de terceros sin modificar su cĂłdigo
- Cuando buscas desacoplar el cliente de la implementaciĂłn concreta