Patrones Estructurales - jonarosero/DesignPattern GitHub Wiki

Patrones Estructurales

Los patrones de diseño estructurales se refieren a la composición de clases y objetos, nos ayuda a simplificar la relación entre objetos, estos patrones nos ayudan a que si hay cambios, sean mínimos y no afecten a toda la aplicación; esto quiere decir que podemos agregar nueva funcionalidad o actualizar sin modificar nuestra aplicación completa.

Adapter

Nivel de aplicación

Clases y Objetos

Definición

El patrón adaptador se utiliza para transformar una interfaz en otra, de tal modo que una clase que no pueda utilizar la primera haga uso de ella a través de la segunda.

Cuando utilizarlo

Este patrón es muy usado cuando se refactoriza una API, donde, al final ya no es compatible con la original, entonces, creamos una especie de adaptador (que es otra clase) para que ambas puedan trabajar juntas en la aplicación.

Beneficios

  • Cumple con el principio de responsabilidad única
  • Cumple con el principio de abierto/cerrado, al introducir nuevos adaptadores sin romper el código del cliente

Desventajas

  • La complejidad del código aumenta, ya que se necesita introducir un conjunto de nuevas interfaces y clases.

Estructura

Ejemplo

Lenguaje escogido: Javascript

Vamos a tomar uno de los ejemplos básicos de la programación: crear una calculador con 3 atributos, dos números y la operación.

///Calculadora
class Calculator {
  operation(num1, num2, operation) {
    switch (operation) {
      case 'add':
        return num1 + num2;
      case 'sub':
        return num1 - num2;
      default:
        return NaN;
    }
  }
}
export default Calculator;

Pero conforme avanzamos aprendemos que podemos mejorar el código de la calculadora para que nos quede algo así:

///Calculadora mejorada
class NewCalculator {
  add(num1, num2) {
    return num1 + num2;
  }
  sub(num1, num2) {
    return num1 - num2;
  }
  div(num1, num2) {
    return num1 / num2;
  }
}

export default NewCalculator;

El problema es que la vieja calculadora nos gusta, nos da nostalgia por que es la primera que hicimos y no queremos desaprovecharla, pero nuestra nueva no es compatible con la anterior, es aquí que vamos a crear un adaptador.

///Adaptador de la vieja calculadora
import NewCalculator from "./NewCalculator";

class CalculatorAdapter {
  constructor() {
    this.calculator = new NewCalculator();
  }
  operation(num1, num2, operation) {
    switch (operation) {
      case "add":
        return this.calculator.add(num1, num2);
      case "sub":
        return this.calculator.sub(num1, num2);
      case "div":
        return this.calculator.div(num1, num2);
      default:
        return NaN;
    }
  }
}

export default CalculatorAdapter;

Decorator

Nivel de aplicación

Objetos

Definición

El patrón Decorator responde a la necesidad de añadir dinámicamente funcionalidad a un Objeto. Esto nos permite no tener que crear sucesivas clases que hereden de la primera incorporando la nueva funcionalidad, sino otras que la implementan y se asocian a la primera.

Cuando utilizarlo

Cuando se necesite agregar comportamientos adicionales a los objetos en tiempo de ejecución sin romper el código, y no sea posible extender el comportamiento de un objeto usando la herencia.

Beneficios

  • Extender comportamientos sin crear nuevas subclases
  • Agregar o quitar comportamientos durante la ejecucción
  • Se puede usar múltiples decoradores para envolver un objeto

Desventajas

  • Cuando la interfaz de componentes es compleja hace más difícil obtener un decorador correcto.
  • El desempeño decae si existen demasiados decoradores
  • Los decoradores pueden complicar el proceso de creación de instancia de objeto, ya que no solo tienen que crear una instancia del componente, si no que también envolverlo en varios decoradores

Estructura

Ejemplo

Lenguaje escogido: Javascript

Nos acaban de contratar para trabajar en Subway, el menú de esta semana es Sándwich de Queso, Sándwich de Pollo y Sándwich de Jamón. Obviamente cada sándwich tiene un precio distinto. Con esto podemos crear una clase Sandwich y las subclases CheeseSubway, ChickenSubway, HamSubway y entre sus métodos estaría la descripción del precio dependiendo del tipo de sándwich.

///Sándwich
class Sandwich {
  constructor() {
    this._drescription = "Sándwich desconocido";
    this._price = 0;
  }
  set price(price) {
    this._price = price;
  }
  get price() {
    return this._price;
  }
  get drescription() {
    return this._drescription;
  }
  getSandwich() {
    return `Su orden es un Sándwich de : ${this.description} el precio total es: ${this.price}`;
  }
}

export default Sandwich;
///Sándwich de Queso

import Sandwich from "./Sandwich";
class CheeseSubway extends Sandwich {
  constructor(){
    super();
    this.description = 'CheeseSubway';
    this.price = 3;
  }
}

export default CheeseSubway;
///Sándwich de pollo

import ChickenSubway from "./ChickenSubway ";
class ChickenSubway extends Sandwich {
  constructor(){
    super();
    this.description = 'ChickenSubway';
    this.price = 5;
  }
}

export default ChickenSubway;
///Sándwich de Jamón

import Sandwich from "./Sandwich";
class HamSubway extends HamSubway {
  constructor(){
    super();
    this.description = 'HamSubway ';
    this.price = 4;
  }
}

export default HamSubway ;

Pero espera un momento esto es un Subway, y el cliente puede modificar su sándwich, oh no! y ahora quien podrá ayudarnos. La primera idea que se te puede pasar por la cabeza es modificar la clase Sandwich y agregar métodos y atributos para poder colocarle: huevos, tomate, queso, tocino o lechuga, bien problema resulto, espera un momento esto trae otra serie de problemas como:

  • Por cada ingrediente debemos agregar otra propiedad y nuevos métodos
  • Modificar el método get price por cada ingrediente, el exceso de ingredientes no es gratis sabes no podemos regalar hojas de lechuga
  • Y si alguien quiere 3 lonchas de queso???
  • Ni hablar del principio de abierto cerrado

Antes de empezar a llorar recordemos que tenemos patrones de diseño, en este caso usaremos el Decorator. Para que una subclase cumpla como decorador, esta debe tener el mismo supertipo que el objeto al que van a decorar, esto quiere decir que extienden de la clase principal; ahora para cumplir con los principios de SOLID vamos a generar un super decorador, quien es el que va a extender de la clase principal, algo así:

///Super decorador de Sándwich

import Sandwich  from "./Sandwich";

class SandwichDecorator extends Sandwich {
  constructor(sandwich) {
    super();
    this.sandwich = sandwich;
  }
}

export default SandwichDecorator;
///Super decorador de Sándwich

import SandwichDecorator from "./SandwichDecorator";

class BaconDecorator extends SandwichDecorator {
  constructor(sandwich) {
    super(sandwich);
  }
  get description() {
    return this.sandwich.description + " con tócino";
  }
  get price() {
    return this.sandwich.price + 9;
  }
}

class HamDecorator extends SandwichDecorator {
  get description() {
    return this.sandwich.description + " con jamón";
  }
  get price() {
    return this.sandwich.price + 15;
  }
}

class MeatDecorator extends SandwichDecorator {
  constructor(sandwich) {
    super(sandwich);
  }
  get description() {
    return this.sandwich.description + " con carne";
  }
  get price() {
    return this.burger.price + 20;
  }
}

class EggDecorator extends SandwichDecorator {
  constructor(sandwich) {
    super(sandwich);
  }
  get description() {
    return this.sandwich.description + " con huevo";
  }
  get price() {
    return this.sandwich.price + 12;
  }
}

class PickleDecorator extends SandwichDecorator {
  constructor(sandwich) {
    super(sandwich);
  }
  get description() {
    return this.sandwich.description + " con pepinillos";
  }
  get price() {
    return this.sandwich.price + 5;
  }
}

class CheeseDecorator extends SandwichDecorator {
  constructor(sandwich) {
    super(sandwich);
  }
  get description() {
    return this.sandwich.description + " con queso";
  }
  get price() {
    return this.sandwich.price + 9;
  }
}

export {
  CheeseDecorator,
  BaconDecorator,
  EggDecorator,
  HamDecorator,
  MeatDecorator,
  PickleDecorator
};