jueves, 13 de febrero de 2025

Top 5 de la semana

Artículos relacionados

Curso de Patrones de Diseño de Software

Introducción

Los patrones de diseño de software son soluciones probadas a problemas comunes que se encuentran en el desarrollo de software. Estos patrones no solo ayudan a estructurar y organizar el código de manera eficiente, sino que también mejoran la mantenibilidad y escalabilidad de las aplicaciones. En este curso, exploraremos los patrones más comunes, clasificados en tres grandes categorías: patrones creacionales, estructurales y de comportamiento. A lo largo del curso, veremos ejemplos en PHP y TypeScript para comprender cómo aplicarlos en proyectos reales.


1. Patrones Creacionales

Los patrones creacionales se enfocan en la forma de crear objetos de manera controlada para garantizar la flexibilidad y reutilización del código. Estos patrones son ideales cuando queremos separar el proceso de creación de objetos de la lógica de negocio principal.

1.1. Singleton

El patrón Singleton asegura que una clase tenga una única instancia y proporciona un punto global de acceso a ella. Es útil cuando se necesita un control global de una clase (como un logger o una conexión a la base de datos).

PHP:

<?php
class Database {
    private static $instance = null;
    private $connection;

    private function __construct() {
        $this->connection = new PDO('mysql:host=localhost;dbname=test', 'root', '');
    }

    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new Database();
        }
        return self::$instance;
    }

    public function getConnection() {
        return $this->connection;
    }
}
?>

TypeScript:

class Database {
    private static instance: Database;
    private connection: string;

    private constructor() {
        this.connection = "Database connection";
    }

    public static getInstance(): Database {
        if (!Database.instance) {
            Database.instance = new Database();
        }
        return Database.instance;
    }

    public getConnection(): string {
        return this.connection;
    }
}

Pros:

  • Control sobre la instancia única, útil para recursos compartidos (e.g., conexión a la base de datos).
  • Ahorro de memoria y recursos, ya que solo se crea una instancia.

Contras:

  • Puede violar el principio de responsabilidad única, ya que maneja la creación y acceso a la instancia.
  • Difícil de testear debido a la instancia global compartida.

1.2. Factory Method

El patrón Factory Method proporciona una forma de delegar la creación de objetos a clases derivadas. Es ideal para situaciones en las que queremos instanciar objetos sin conocer la clase exacta que se va a utilizar.

PHP:

<?php
interface Vehicle {
    public function create();
}

class Car implements Vehicle {
    public function create() {
        return "Car created!";
    }
}

class Bike implements Vehicle {
    public function create() {
        return "Bike created!";
    }
}

class VehicleFactory {
    public static function createVehicle($type) {
        if ($type === 'car') {
            return new Car();
        } elseif ($type === 'bike') {
            return new Bike();
        }
        throw new Exception("Invalid vehicle type");
    }
}
?>

TypeScript:

interface Vehicle {
    create(): string;
}

class Car implements Vehicle {
    create(): string {
        return "Car created!";
    }
}

class Bike implements Vehicle {
    create(): string {
        return "Bike created!";
    }
}

class VehicleFactory {
    public static createVehicle(type: string): Vehicle {
        switch (type) {
            case 'car':
                return new Car();
            case 'bike':
                return new Bike();
            default:
                throw new Error("Invalid vehicle type");
        }
    }
}

Pros:

  • Flexibilidad para crear diferentes tipos de objetos en tiempo de ejecución.
  • Facilita la extensión para nuevos tipos de objetos sin modificar el código existente.

Contras:

  • Puede introducir complejidad innecesaria si solo se requiere un tipo simple de objeto.
  • Aumenta el número de clases en el sistema.

2. Patrones Estructurales

Los patrones estructurales se centran en cómo organizar las clases y objetos para formar estructuras más grandes y flexibles. Estos patrones son útiles para gestionar relaciones y simplificar la arquitectura del sistema.

2.1. Adapter

El patrón Adapter convierte la interfaz de una clase en otra que el cliente espera. Es ideal cuando queremos integrar una clase existente que no cumple con la interfaz que necesitamos.

PHP:

<?php
interface PaymentGateway {
    public function pay($amount);
}

class Stripe {
    public function makePayment($amount) {
        return "Paid $amount with Stripe.";
    }
}

class StripeAdapter implements PaymentGateway {
    private $stripe;

    public function __construct(Stripe $stripe) {
        $this->stripe = $stripe;
    }

    public function pay($amount) {
        return $this->stripe->makePayment($amount);
    }
}
?>

TypeScript:

interface PaymentGateway {
    pay(amount: number): string;
}

class Stripe {
    makePayment(amount: number): string {
        return `Paid ${amount} with Stripe.`;
    }
}

class StripeAdapter implements PaymentGateway {
    private stripe: Stripe;

    constructor(stripe: Stripe) {
        this.stripe = stripe;
    }

    pay(amount: number): string {
        return this.stripe.makePayment(amount);
    }
}

Pros:

  • Permite la integración de clases con interfaces incompatibles.
  • Facilita la reutilización de código existente sin modificarlo.

Contras:

  • Puede añadir complejidad adicional si se utilizan muchos adaptadores.
  • El código puede volverse difícil de mantener si se abusa de este patrón.

3. Patrones de Comportamiento

Los patrones de comportamiento se enfocan en la interacción y comunicación entre los objetos. Estos patrones son útiles para definir cómo los objetos cooperan y responden ante ciertos eventos.

3.1. Observer

El patrón Observer define una relación de uno a muchos entre objetos, de manera que cuando uno cambia de estado, se notifica a todos los objetos dependientes. Es útil en sistemas donde se necesita una actualización reactiva.

PHP:

<?php
interface Observer {
    public function update($state);
}

class ConcreteObserver implements Observer {
    public function update($state) {
        echo "State updated to $state";
    }
}

class Subject {
    private $observers = [];
    private $state;

    public function attach(Observer $observer) {
        $this->observers[] = $observer;
    }

    public function setState($state) {
        $this->state = $state;
        $this->notify();
    }

    private function notify() {
        foreach ($this->observers as $observer) {
            $observer->update($this->state);
        }
    }
}
?>

TypeScript:

interface Observer {
    update(state: string): void;
}

class ConcreteObserver implements Observer {
    update(state: string): void {
        console.log(`State updated to ${state}`);
    }
}

class Subject {
    private observers: Observer[] = [];
    private state: string;

    attach(observer: Observer): void {
        this.observers.push(observer);
    }

    setState(state: string): void {
        this.state = state;
        this.notify();
    }

    private notify(): void {
        this.observers.forEach(observer => observer.update(this.state));
    }
}

Pros:

  • Facilita la implementación de sistemas reactivos.
  • Reduce el acoplamiento entre el sujeto y los observadores.

Contras:

  • Puede ser complicado gestionar muchos observadores.
  • El rendimiento puede verse afectado si hay una gran cantidad de actualizaciones.

2.2. Decorator

El patrón Decorator permite añadir funcionalidades a un objeto de manera dinámica sin modificar su estructura original. Este patrón es útil cuando se quiere extender las capacidades de una clase de forma flexible y escalable.

PHP:

<?php
interface Coffee {
    public function cost();
}

class SimpleCoffee implements Coffee {
    public function cost() {
        return 5;
    }
}

class MilkDecorator implements Coffee {
    protected $coffee;

    public function __construct(Coffee $coffee) {
        $this->coffee = $coffee;
    }

    public function cost() {
        return $this->coffee->cost() + 2;
    }
}

class SugarDecorator implements Coffee {
    protected $coffee;

    public function __construct(Coffee $coffee) {
        $this->coffee = $coffee;
    }

    public function cost() {
        return $this->coffee->cost() + 1;
    }
}

// Uso
$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);
echo $coffee->cost(); // Salida: 8
?>

TypeScript:

interface Coffee {
    cost(): number;
}

class SimpleCoffee implements Coffee {
    cost(): number {
        return 5;
    }
}

class MilkDecorator implements Coffee {
    constructor(private coffee: Coffee) {}

    cost(): number {
        return this.coffee.cost() + 2;
    }
}

class SugarDecorator implements Coffee {
    constructor(private coffee: Coffee) {}

    cost(): number {
        return this.coffee.cost() + 1;
    }
}

// Uso
let coffee: Coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
console.log(coffee.cost()); // Output: 8

Pros:

  • Permite añadir funcionalidades de forma dinámica y flexible.
  • Fomenta la reutilización de código y cumple con el principio de responsabilidad única.

Contras:

  • Puede generar muchas clases adicionales, lo cual podría complicar el mantenimiento del sistema.
  • La creación de objetos decorados puede volverse compleja si se usan muchos decoradores.

2.3. Composite

El patrón Composite permite tratar de manera uniforme objetos individuales y compuestos (formados por múltiples objetos). Este patrón es especialmente útil para representar jerarquías de objetos, como estructuras de carpetas y archivos en un sistema de archivos.

PHP:

<?php
interface FileSystemComponent {
    public function display($indentation);
}

class File implements FileSystemComponent {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function display($indentation) {
        echo str_repeat("-", $indentation) . $this->name . PHP_EOL;
    }
}

class Directory implements FileSystemComponent {
    private $name;
    private $children = [];

    public function __construct($name) {
        $this->name = $name;
    }

    public function add(FileSystemComponent $component) {
        $this->children[] = $component;
    }

    public function display($indentation) {
        echo str_repeat("-", $indentation) . $this->name . PHP_EOL;
        foreach ($this->children as $child) {
            $child->display($indentation + 2);
        }
    }
}

// Uso
$root = new Directory("root");
$root->add(new File("file1.txt"));
$subDir = new Directory("subDir");
$subDir->add(new File("file2.txt"));
$root->add($subDir);
$root->display(0);
?>

TypeScript:

interface FileSystemComponent {
    display(indentation: number): void;
}

class File implements FileSystemComponent {
    constructor(private name: string) {}

    display(indentation: number): void {
        console.log(`${'-'.repeat(indentation)}${this.name}`);
    }
}

class Directory implements FileSystemComponent {
    private children: FileSystemComponent[] = [];

    constructor(private name: string) {}

    add(component: FileSystemComponent): void {
        this.children.push(component);
    }

    display(indentation: number): void {
        console.log(`${'-'.repeat(indentation)}${this.name}`);
        this.children.forEach(child => child.display(indentation + 2));
    }
}

// Uso
const root = new Directory("root");
root.add(new File("file1.txt"));
const subDir = new Directory("subDir");
subDir.add(new File("file2.txt"));
root.add(subDir);
root.display(0);

Pros:

  • Facilita el trabajo con estructuras jerárquicas de objetos.
  • Simplifica la manipulación de objetos complejos mediante una interfaz común.

Contras:

  • La implementación puede ser complicada si las jerarquías son demasiado profundas.
  • Puede violar el principio de responsabilidad única al mezclar objetos compuestos e individuales.

3.2. Strategy

El patrón Strategy define una familia de algoritmos, encapsulándolos para que sean intercambiables. Este patrón es útil cuando necesitamos variar el comportamiento de un objeto en tiempo de ejecución.

PHP:

<?php
interface PaymentStrategy {
    public function pay($amount);
}

class CreditCardPayment implements PaymentStrategy {
    public function pay($amount) {
        return "Paid $amount using Credit Card";
    }
}

class PayPalPayment implements PaymentStrategy {
    public function pay($amount) {
        return "Paid $amount using PayPal";
    }
}

class PaymentContext {
    private $strategy;

    public function setStrategy(PaymentStrategy $strategy) {
        $this->strategy = $strategy;
    }

    public function executePayment($amount) {
        return $this->strategy->pay($amount);
    }
}

// Uso
$context = new PaymentContext();
$context->setStrategy(new CreditCardPayment());
echo $context->executePayment(100); // Salida: Paid 100 using Credit Card
?>

TypeScript:

interface PaymentStrategy {
    pay(amount: number): string;
}

class CreditCardPayment implements PaymentStrategy {
    pay(amount: number): string {
        return `Paid ${amount} using Credit Card`;
    }
}

class PayPalPayment implements PaymentStrategy {
    pay(amount: number): string {
        return `Paid ${amount} using PayPal`;
    }
}

class PaymentContext {
    private strategy: PaymentStrategy;

    setStrategy(strategy: PaymentStrategy): void {
        this.strategy = strategy;
    }

    executePayment(amount: number): string {
        return this.strategy.pay(amount);
    }
}

// Uso
const context = new PaymentContext();
context.setStrategy(new CreditCardPayment());
console.log(context.executePayment(100)); // Output: Paid 100 using Credit Card

Pros:

  • Facilita la extensión y modificación de algoritmos sin alterar el código del cliente.
  • Promueve el principio abierto/cerrado (OCP).

Contras:

  • Requiere que el cliente conozca las estrategias disponibles para seleccionarlas.
  • Puede incrementar la complejidad del código si se usan muchas estrategias.

3.3. Command

El patrón Command convierte las solicitudes en objetos, permitiendo parametrizar y almacenar las acciones que se desean ejecutar. Es útil para sistemas que requieren comandos reversibles o que necesitan programar la ejecución de tareas.

PHP:

<?php
interface Command {
    public function execute();
}

class LightOnCommand implements Command {
    public function execute() {
        echo "Light turned on";
    }
}

class LightOffCommand implements Command {
    public function execute() {
        echo "Light turned off";
    }
}

class RemoteControl {
    private $command;

    public function setCommand(Command $command) {
        $this->command = $command;
    }

    public function pressButton() {
        $this->command->execute();
    }
}

// Uso
$remote = new RemoteControl();
$remote->setCommand(new LightOnCommand());
$remote->pressButton(); // Salida: Light turned on
?>

TypeScript:

interface Command {
    execute(): void;
}

class LightOnCommand implements Command {
    execute(): void {
        console.log("Light turned on");
    }
}

class LightOffCommand implements Command {
    execute(): void {
        console.log("Light turned off");
    }
}

class RemoteControl {
    private command: Command;

    setCommand(command: Command): void {
        this.command = command;
    }

    pressButton(): void {
        this.command.execute();
    }
}

// Uso
const remote = new RemoteControl();
remote.setCommand(new LightOnCommand());
remote.pressButton(); // Output: Light turned on

Pros:

  • Simplifica la ejecución de comandos y permite deshacer o repetir acciones.
  • Facilita la implementación de sistemas de logging y gestión de acciones programadas.

Contras:

  • Aumenta el número de clases en el sistema.
  • La lógica puede volverse compleja si se usan muchos comandos interdependientes.

3.4. Chain of Responsibility

El patrón Chain of Responsibility permite que un conjunto de objetos maneje una solicitud en cadena hasta que uno de ellos la procese. Es útil cuando se desea evitar el acoplamiento entre el emisor de una solicitud y su receptor, y cuando se tiene una secuencia de objetos que podrían manejar dicha solicitud.

PHP:

<?php
abstract class Handler {
    protected $nextHandler;

    public function setNext(Handler $handler) {
        $this->nextHandler = $handler;
    }

    public function handle($request) {
        if ($this->nextHandler) {
            return $this->nextHandler->handle($request);
        }
        return null;
    }
}

class AuthHandler extends Handler {
    public function handle($request) {
        if ($request === "auth") {
            return "Authorization Successful";
        }
        return parent::handle($request);
    }
}

class LogHandler extends Handler {
    public function handle($request) {
        if ($request === "log") {
            return "Log Entry Created";
        }
        return parent::handle($request);
    }
}

// Uso
$auth = new AuthHandler();
$log = new LogHandler();
$auth->setNext($log);

echo $auth->handle("log"); // Salida: Log Entry Created
?>

TypeScript:

abstract class Handler {
    protected nextHandler: Handler;

    setNext(handler: Handler): void {
        this.nextHandler = handler;
    }

    handle(request: string): string | null {
        if (this.nextHandler) {
            return this.nextHandler.handle(request);
        }
        return null;
    }
}

class AuthHandler extends Handler {
    handle(request: string): string | null {
        if (request === "auth") {
            return "Authorization Successful";
        }
        return super.handle(request);
    }
}

class LogHandler extends Handler {
    handle(request: string): string | null {
        if (request === "log") {
            return "Log Entry Created";
        }
        return super.handle(request);
    }
}

// Uso
const auth = new AuthHandler();
const log = new LogHandler();
auth.setNext(log);

console.log(auth.handle("log")); // Output: Log Entry Created

Pros:

  • Desacopla el emisor de una solicitud de sus posibles receptores.
  • Flexibilidad para añadir o cambiar handlers sin modificar el código existente.

Contras:

  • Puede ser difícil de depurar debido a la cadena de responsabilidad dinámica.
  • El rendimiento puede verse afectado si la cadena de handlers es muy larga.

4. Patrones Menos Comunes

Ahora que hemos cubierto los patrones más comunes, vamos a explorar algunos patrones menos conocidos pero que también pueden ser de gran utilidad en situaciones específicas.

4.1. Flyweight

El patrón Flyweight minimiza el uso de memoria al compartir la mayor cantidad posible de datos con objetos similares. Es útil cuando se tiene un gran número de objetos que comparten información común, como en sistemas gráficos o juegos.

PHP:

<?php
class Tree {
    private $type;
    private static $instances = [];

    private function __construct($type) {
        $this->type = $type;
    }

    public static function getInstance($type) {
        if (!isset(self::$instances[$type])) {
            self::$instances[$type] = new Tree($type);
        }
        return self::$instances[$type];
    }

    public function display($location) {
        echo "Displaying " . $this->type . " tree at " . $location . PHP_EOL;
    }
}

// Uso
$pineTree = Tree::getInstance("Pine");
$oakTree = Tree::getInstance("Oak");

$pineTree->display("Park");
$oakTree->display("Garden");
?>

TypeScript:

class Tree {
    private static instances: { [key: string]: Tree } = {};

    private constructor(private type: string) {}

    static getInstance(type: string): Tree {
        if (!Tree.instances[type]) {
            Tree.instances[type] = new Tree(type);
        }
        return Tree.instances[type];
    }

    display(location: string): void {
        console.log(`Displaying ${this.type} tree at ${location}`);
    }
}

// Uso
const pineTree = Tree.getInstance("Pine");
const oakTree = Tree.getInstance("Oak");

pineTree.display("Park");
oakTree.display("Garden");

Pros:

  • Optimiza el uso de memoria mediante la reutilización de objetos.
  • Ideal para aplicaciones que manejan un gran número de objetos similares.

Contras:

  • La implementación puede volverse compleja si los objetos tienen muchos estados compartidos y no compartidos.
  • No siempre es aplicable, especialmente cuando los objetos tienen estados independientes.

4.2. Memento

El patrón Memento permite capturar y restaurar el estado de un objeto sin revelar los detalles de su implementación. Es útil en sistemas donde se requiere implementar deshacer y rehacer operaciones.

PHP:

<?php
class Memento {
    private $state;

    public function __construct($state) {
        $this->state = $state;
    }

    public function getState() {
        return $this->state;
    }
}

class Originator {
    private $state;

    public function setState($state) {
        $this->state = $state;
    }

    public function saveState() {
        return new Memento($this->state);
    }

    public function restoreState(Memento $memento) {
        $this->state = $memento->getState();
    }
}

// Uso
$originator = new Originator();
$originator->setState("State1");
$memento = $originator->saveState();
$originator->setState("State2");

$originator->restoreState($memento);
?>

TypeScript:

class Memento {
    constructor(private state: string) {}

    getState(): string {
        return this.state;
    }
}

class Originator {
    private state: string;

    setState(state: string): void {
        this.state = state;
    }

    saveState(): Memento {
        return new Memento(this.state);
    }

    restoreState(memento: Memento): void {
        this.state = memento.getState();
    }
}

// Uso
const originator = new Originator();
originator.setState("State1");
const memento = originator.saveState();
originator.setState("State2");

originator.restoreState(memento);

Pros:

  • Facilita la implementación de funciones de deshacer/rehacer en una aplicación.
  • Protege la encapsulación al no exponer detalles internos del objeto.

Contras:

  • Puede aumentar el consumo de memoria si se guardan demasiados estados.
  • La gestión de los mementos puede volverse compleja en sistemas grandes.

4.3. Visitor

El patrón Visitor permite añadir operaciones a clases sin modificarlas. Es útil cuando se necesita aplicar múltiples operaciones a objetos de una estructura compleja y se quiere evitar modificar cada clase para implementar esas operaciones.

PHP:

<?php
interface Element {
    public function accept(Visitor $visitor);
}

class ConcreteElementA implements Element {
    public function accept(Visitor $visitor) {
        $visitor->visitElementA($this);
    }
}

class ConcreteElementB implements Element {
    public function accept(Visitor $visitor) {
        $visitor->visitElementB($this);
    }
}

interface Visitor {
    public function visitElementA(ConcreteElementA $element);
    public function visitElementB(ConcreteElementB $element);
}

class ConcreteVisitor implements Visitor {
    public function visitElementA(ConcreteElementA $element) {
        echo "Visiting Element A";
    }

    public function visitElementB(ConcreteElementB $element) {
        echo "Visiting Element B";
    }
}

// Uso
$elementA = new ConcreteElementA();
$visitor = new ConcreteVisitor();
$elementA->accept($visitor);
?>

TypeScript:

interface Element {
    accept(visitor: Visitor): void;
}

class ConcreteElementA implements Element {
    accept(visitor: Visitor): void {
        visitor.visitElementA(this);
    }
}

class ConcreteElementB implements Element {
    accept(visitor: Visitor): void {
        visitor.visitElementB(this);
    }
}

interface Visitor {
    visitElementA(element: ConcreteElementA): void;
    visitElementB(element: ConcreteElementB): void;
}

class ConcreteVisitor implements Visitor {
    visitElementA(element: ConcreteElementA): void {
        console.log("Visiting Element A");
    }

    visitElementB(element: ConcreteElementB): void {
        console.log("Visiting Element B");
    }
}

// Uso
const elementA = new ConcreteElementA();
const visitor = new ConcreteVisitor();
elementA.accept(visitor);

Pros:

  • Facilita la adición de nuevas operaciones sin modificar las clases originales.
  • Desacopla las operaciones de las clases, mejorando la mantenibilidad.

Contras:

  • Puede resultar complicado mantener y escalar la estructura si hay muchos elementos y operaciones.
  • Introduce complejidad al código al tener que implementar múltiples visitantes.

4.4. State

El patrón State permite que un objeto altere su comportamiento cuando cambia su estado interno. Es útil cuando un objeto necesita cambiar su comportamiento en tiempo de ejecución dependiendo de su estado.

PHP:

<?php
interface State {
    public function handle();
}

class OnState implements State {
    public function handle() {
        echo "The device is now ON.";
    }
}

class OffState implements State {
    public function handle() {
        echo "The device is now OFF.";
    }
}

class Device {
    private $state;

    public function setState(State $state) {
        $this->state = $state;
    }

    public function pressButton() {
        $this->state->handle();
    }
}

// Uso
$device = new Device();
$onState = new OnState();
$offState = new OffState();

$device->setState($onState);
$device->pressButton(); // Salida: The device is now ON.

$device->setState($offState);
$device->pressButton(); // Salida: The device is now OFF.
?>

TypeScript:

interface State {
    handle(): void;
}

class OnState implements State {
    handle(): void {
        console.log("The device is now ON.");
    }
}

class OffState implements State {
    handle(): void {
        console.log("The device is now OFF.");
    }
}

class Device {
    private state: State;

    setState(state: State): void {
        this.state = state;
    }

    pressButton(): void {
        this.state.handle();
    }
}

// Uso
const device = new Device();
const onState = new OnState();
const offState = new OffState();

device.setState(onState);
device.pressButton(); // Output: The device is now ON.

device.setState(offState);
device.pressButton(); // Output: The device is now OFF.

Pros:

  • Facilita la adición de nuevos estados y comportamientos sin modificar la clase principal.
  • Mantiene el principio de responsabilidad única al encapsular comportamientos específicos.

Contras:

  • Puede incrementar el número de clases en el sistema.
  • La complejidad puede aumentar si se tienen demasiados estados y transiciones entre ellos.

4.5. Mediator

El patrón Mediator define un objeto que controla la interacción entre un conjunto de objetos, promoviendo el desacoplamiento. Es útil en sistemas donde varios objetos interactúan de manera compleja y se desea evitar referencias directas entre ellos.

PHP:

<?php
interface Mediator {
    public function notify($sender, $event);
}

class ConcreteMediator implements Mediator {
    private $component1;
    private $component2;

    public function setComponent1($component) {
        $this->component1 = $component;
    }

    public function setComponent2($component) {
        $this->component2 = $component;
    }

    public function notify($sender, $event) {
        if ($event == "A") {
            echo "Mediator reacts to A and triggers B.";
            $this->component2->doB();
        }
    }
}

class Component1 {
    private $mediator;

    public function setMediator(Mediator $mediator) {
        $this->mediator = $mediator;
    }

    public function doA() {
        echo "Component1 does A.";
        $this->mediator->notify($this, "A");
    }
}

class Component2 {
    private $mediator;

    public function setMediator(Mediator $mediator) {
        $this->mediator = $mediator;
    }

    public function doB() {
        echo "Component2 does B.";
    }
}

// Uso
$mediator = new ConcreteMediator();
$component1 = new Component1();
$component2 = new Component2();

$component1->setMediator($mediator);
$component2->setMediator($mediator);

$mediator->setComponent1($component1);
$mediator->setComponent2($component2);

$component1->doA();
?>

TypeScript:

interface Mediator {
    notify(sender: object, event: string): void;
}

class ConcreteMediator implements Mediator {
    private component1: Component1;
    private component2: Component2;

    setComponent1(component: Component1): void {
        this.component1 = component;
    }

    setComponent2(component: Component2): void {
        this.component2 = component;
    }

    notify(sender: object, event: string): void {
        if (event === "A") {
            console.log("Mediator reacts to A and triggers B.");
            this.component2.doB();
        }
    }
}

class Component1 {
    private mediator: Mediator;

    setMediator(mediator: Mediator): void {
        this.mediator = mediator;
    }

    doA(): void {
        console.log("Component1 does A.");
        this.mediator.notify(this, "A");
    }
}

class Component2 {
    private mediator: Mediator;

    setMediator(mediator: Mediator): void {
        this.mediator = mediator;
    }

    doB(): void {
        console.log("Component2 does B.");
    }
}

// Uso
const mediator = new ConcreteMediator();
const component1 = new Component1();
const component2 = new Component2();

component1.setMediator(mediator);
component2.setMediator(mediator);

mediator.setComponent1(component1);
mediator.setComponent2(component2);

component1.doA();

Pros:

  • Reduce las dependencias entre componentes, promoviendo un menor acoplamiento.
  • Facilita la escalabilidad y el mantenimiento al centralizar la comunicación.

Contras:

  • El Mediador puede volverse complejo y convertirse en un «objeto dios» si maneja demasiada lógica.
  • Puede ser difícil de mantener si el Mediador central gestiona muchas interacciones.

4.6. Interpreter

El patrón Interpreter es útil cuando se tiene un lenguaje específico o un conjunto de reglas que se quieren evaluar o interpretar en tiempo de ejecución. Es común en aplicaciones que requieren un analizador para evaluar expresiones, como calculadoras, filtros de búsqueda, o incluso compiladores simples.

PHP:

<?php
interface Expression {
    public function interpret($context);
}

class NumberExpression implements Expression {
    private $number;

    public function __construct($number) {
        $this->number = $number;
    }

    public function interpret($context) {
        return $this->number;
    }
}

class AddExpression implements Expression {
    private $leftExpression;
    private $rightExpression;

    public function __construct($left, $right) {
        $this->leftExpression = $left;
        $this->rightExpression = $right;
    }

    public function interpret($context) {
        return $this->leftExpression->interpret($context) + $this->rightExpression->interpret($context);
    }
}

// Uso
$left = new NumberExpression(5);
$right = new NumberExpression(10);
$add = new AddExpression($left, $right);

echo $add->interpret(null); // Salida: 15
?>

TypeScript:

interface Expression {
    interpret(context: any): number;
}

class NumberExpression implements Expression {
    constructor(private number: number) {}

    interpret(context: any): number {
        return this.number;
    }
}

class AddExpression implements Expression {
    constructor(private left: Expression, private right: Expression) {}

    interpret(context: any): number {
        return this.left.interpret(context) + this.right.interpret(context);
    }
}

// Uso
const left = new NumberExpression(5);
const right = new NumberExpression(10);
const add = new AddExpression(left, right);

console.log(add.interpret(null)); // Output: 15

Pros:

  • Facilita la implementación de evaluadores para lenguajes específicos.
  • Es flexible y se puede extender fácilmente con nuevas reglas y expresiones.

Contras:

  • No es eficiente para lenguajes complejos, ya que puede volverse lento y consumir mucha memoria.
  • La implementación puede volverse compleja si hay muchas reglas y combinaciones posibles.

Resumen Final de Patrones Menos Comunes

En esta sección hemos cubierto patrones menos conocidos, pero útiles en contextos específicos. Estos patrones permiten gestionar estados, optimizar memoria, manejar la comunicación entre componentes, y evaluar reglas de manera dinámica. Aunque no son tan utilizados como otros patrones más comunes, dominarlos puede ser una ventaja en proyectos con necesidades específicas o arquitecturas complejas.

Jordi Morillo
Jordi Morillohttps://www.programador-web.com
Soy un programador PHP Senior con más de 20 años de experiencia en desarrollo web y administración de sistemas Linux, especializado en Symfony y metodologías ágiles como Scrum. He trabajado con tecnologías como MySQL, MongoDB y WordPress, y siempre busco nuevas oportunidades para seguir aprendiendo y aplicando mis conocimientos.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!
Por favor ingrese su nombre aquí

Artículos populares