Tabla de Contenidos

Dessign Patterns

Creational Patterns

Abstract Factory

El patrón Abstract Factory se basa en un objeto que clasifica los objetos por famílias y crea dichos objetos según la família demandada.
UML

// AbstractFactory
interface Factory {
  Button getButton ();
  TextBox getTextBox();
}
 
// ConcreteFactory1
class FactoryFramework1 implements AbstractFactory {
  Button getButton () {
    // Retorna un botón del framework 1
  }
  TextBox getTextBox () { ... }
}
 
// ConcreteFactory2
class FactoryFramework2 implements AbstractFactory {
  Button getButton () {
    // Retorna un botón del framework 2
  }
  TextBox getTextBox () { ... }
}
 
// AbstractProductA
interface Button {
  public void Click ();
}
 
// ConcreteProductA1
class ButtonFramework1 extends GKButton implements Button {
  public void Click () {
    this.pushButton();
  }
}
 
// ConcreteProductB1
class ButtonFramework2 extends QTButton implements Button {
  public void Click () {
    this.ClickEvent();
  }
}
 
// Client
Factory fact = new FactoryFramework1 ();
Button b = fact.getButton();               // Devolverá el botón de la família demandada (1)

Builder

Separa la construcción del objeto de su interacción con él, para ello se encapsula los métodos de creación en una interface y es a partir de los métodos de esta con la que crearemos el objeto.

UML En el UML…

// Clase Product
public class Vehicle {
  // getters y setters de sus propiedades
}
 
// Clase Builder
abstract class VehicleBuilder {
    protected Vehicle vehicle;
    public VehicleBuilder () {
       this.vehicle = new Vehicle();
    }
 
    public Vehicle getVehicle {
      return this.vehicle;
    }
 
    public abstract void BuildEngine();
    public abstract void BuildWheels();
    public abstract void BuildDoors();
}
 
// ConcretBuilder 1
class MotorCycleBuilder : VehicleBuilder {
    public override void BuildEngine() {
      vehicle.setEngine(500);
    }
 
    public override void BuildWheels() {
      vehicle.setWheels(2);
    }
 
    public override void BuildDoors() {
      vehicle.setDoors(0);
    }
}
 
// ConcreteBuilder2
class CarBuilder : VehicleBuilder {
    public override void BuildEngine() {
      vehicle.setEngine(2500);
    }
 
    public override void BuildWheels() {
      vehicle.setWheels(4);
    }
 
    public override void BuildDoors() {
      vehicle.setDoors(4);
    }
}
 
// Clase Director
class Shop {
    public static Vehicle Construct(VehicleBuilder vehicleBuilder) {
      vehicleBuilder.BuildEngine();
      vehicleBuilder.BuildWheels();
      vehicleBuilder.BuildDoors();
      return vehicleBuilder.getVehicle();
    }
}
 
// Construir un coche:
Vehicle myCar = Shop.Construct (new CarBuilder());

Factory Method

Cuando tenemos que escoger entre varias clases relacionadas para crear un objeto utilizaremos el Factory Method, este se basa en la idea de una creación dinámica, que cambia según las necesidades del objeto que está a punto de crearse.
Para ello se utiliza una interfaz (o clase abstracta), llamada creadora de productos, que contiene el método factoría (factory method) este devuelve objetos del tipo Producto, que es una interface de la cual heredan los productos concretos. El método factoría, el que devuelve los productos, lo que hace es “configurar” un producto concreto, dando a este las propiedades que lo distinguen de los demás productos concretos. La clave para realizar dicha “configuración” viene dada por las clases creadoras concretas que heredan de la creadora de productos y sobreescriben el método factoría, cada una de ellas devuelve en este método el producto concreto que le toca personalizando las propiedades de este.
UML
Por ejemplo, idearemos un programa que te crea textos concretos (libros, articulos, ensayos, libros de referencia…), para ello configura el texto con diversas secciones. En este caso la interface Seccion sería el producto, cada una de las clases que heredasen de Seccion serían los productos concretos. La clase abstracta Texto será la creadora de productos y cada tipo de textos los creadores concretos. El método factoría sería el configSecciones.

// Producto
interface Seccion { ... };
 
// Productos concretos
class Indice : Seccion { ... };
class Presentacion : Seccion { ... };
class Introduccion : Seccion { ... };
class Capitulo : Seccion { ... };
class Prologo : Seccion { ... };
class Epilogo : Seccion { ... };
class Anexo : Seccion { ... };
 
// Creadora de productos
abstract class Texto {
   LinkedList secciones = new LinkedList();
   public Texto {
      this.configSecciones ();
   }
   public abstract void configSecciones ();
};
 
// Producto concreto: Libro
class Libro : Texto {
  public void configSecciones () {
    this.secciones.add (new Introduccion ());
    this.secciones.add (new Prologo ());
    this.secciones.add (new Capitulo ());
    this.secciones.add (new Epilogo ());
  }
}
 
// Producto concreto: Articulo
class Articulo : Texto {
  public void configSecciones () {
    this.secciones.add (new Indice ());
    this.secciones.add (new Introduccion ());
    this.secciones.add (new Capitulo ());
  }
}

Utilizaríamos el ejemplo de anterior creando objetos de la siguiente manera:

Texto texto1 = new Libro ();
Texto texto2 = new Articulo ();

Prototype

Especifica que la aparición de un nuevo objeto en nuestro escenario programado no tiene por qué crearse como uno nuevo sino que puede ser una copia de otro objeto ya creado previamente.
Algunos frameworks ya proveen interfaces que facilitan la implementación de este patrón, por ejemplo Java permite usar la interface Cloneable que contiene un método Clone. Para implementar el patrón prototype en una clase própia debemos implementar esta interface y sobreescribir este método realizando y devolviendo en él una copia del objeto.

UML

Singleton

Su propósito es asegurar que sólo existe una única instancia de una clase para todo el programa y que esta sea de acceso global. Para ello el acceso al constructor ha de ser privado, o como muy permisivo protegido. La clase deberá de tener de forma estática y privada una instancia al objeto único que existirá y un método estático público que devolverá dicha instancia; de esa forma, el método puede acceder al constructor y al objeto para crearlo y devolverlo (sólo lo creará la primera vez) pero desde fuera de la clase no se podrán crear otros objetos.

public class Singleton {
    private static Singleton INSTANCE = null;
    private Singleton() {}
 
    private synchronized static void createInstance() {
        if (INSTANCE == null)
            INSTANCE = new Singleton();
    }
 
    public static Singleton getInstance() {
        if (INSTANCE == null) 
            createInstance();
        return INSTANCE;
    }
}

Structural Patterns

Adapter

Consiste en adaptar una clase a otra; en otras palabras, modificar una clase (clase adaptada) mediante otra (clase adaptadora) para que otra (clase cliente) la utilice. Sus usos son:

Existen dos formas de llevar a cabo el patrón adaptador:

UML Otro ejemplo. Desarrollamos una librería para dibujar figuras, conocemos como dibujar línias y cuadrados y para ello creamos las clases Line y Square que heredan de Shape, para dibujar círculos creamos la clase Circle pero como no sabemos dibujarlos utilizamos una librería que contiene una clase que los dibuja, la XCircle. Nuestra clase Circle contendrá un objeto XCircle y llamará a los métodos de este cuando se llame a los suyos.

Bridge

En un objeto separa la abstracción (interface, clase abstracta de la que hereda) de la implementación (codificación) para agilizar así el cambio de estas independientemente, de esa forma mejora la extensivilidad del código y, a la vez, esconde la implementación a los clientes.
Esquema
En el esquema tenemos una clase abstracta Window que tiene un objeto interno del tipo WindowImp, este es una interface de la que heredan XWindowImp y PMWindowImp. Debemos saber con antelación qué motor implementará la ventana (XWindow o PMWindow), y cuando hagamos alguna acción sobre esta se llamará a los métodos de la instancia WindowImp que tiene. Las implementaciones IconWindow y TransientWindow no son más que tipos de ventana específicos.

UML

Composite

Es la cualidad de crear objetos complejos a partir de otros más sencillos y similares, aprovechando una estructura recursiva y arbolea.
 UML y Esquema

UML La función de los elementos que componen este diagrama es:

Decorator

Añaden funcionalidad extra, dinámica y externamente a una o varias clases.
Por ejemplo un objeto cuadro de texto al que queremos asignar unas barras de scroll, estas son otro objeto de otra clase y la funcionalidad que le añaden es la de la movilidad por el texto. El objeto Decorator sería el correspondiente a las barras de scroll, y como tal, debe contener una lista de los objetos a los que afecta; aunque en este caso concreto ha sido únicamente un cuadro de texto podría haber sido un objeto Libro, Vídeo… a los que se les añade la capacidad de ser prestados.

UML

Facade

En este patrón una única clase representa todo un conjunto de clases y de acciones, la gracia está en que esta clase ha de proveer una forma sencilla de utilizar el sistema al que reemplaza. Ha de proveer clases sencillas que sirvan para tareas concretas.
Este patrón es útil, por ejemplo, cuando…

Esquema
* La diferencia entre el patrón adaptador y el facade es que el primero adapta (amplia, mejora, restringe…) un código, mientras que el facade facilita el uso.

Flyweight

Su objetivo es optimizar el uso de la memoria agrupando en un objeto (FlyweightObject) propiedades comunes de otros objetos; el FlyweightObject estará contenido como una referencia dentro de los demás que tengan esas propiedades comunes y de igual valor.
Por ejemplo, imaginemos 100 objetos pelota, cada uno de ellos con un diámetro, un color y una posición en pantalla. De esas 100 pelotas 75 son rojas y de diámetro 4, las otras 25 azules y de diámetro 5; lo único que cambia para cada una de ellas es la posición. El FlyweightObject contendría las propiedades color y diámetro y las pelotas una referencia a un objeto FlyweightObject y un objeto Point que indica la posición, existirían sólo dos objetos FlyweightObject (uno para las rojas de diámetro 4 y otro para las azules de diámetro 5) y 100 objetos pelota con sus 100 objetos Point.
Otro ejemplo sería el de carácteres de un párrafo en un procesador de textos, cada carácter contiene un formato concreto (fuente, tamaño, negrita…), pero en general todos contienen las mismas propiedades, es correcto que ellos almacenen un FlyweightObject.

Proxy

Se utiliza un intermediario entre varios objetos para controlar el acceso de uno a otro. Para ello el cliente tiene acceso a un objeto Proxy que enmascara otro objeto, el que realmente está utilizando el cliente, es decir, dentro del Proxy se llaman a los métodos del objeto real permitiendo\impidiendo que se realicen acciones sobre este.

UML
El patrón Proxy permite las siguientes variantes:

Proxy Virtual

// Subject
interface Image {
   public void displayImage();
}
 
// RealSubject
class RealImage implements Image {
   private String filename;
   public RealImage(String filename) { 
       this.filename = filename;        
       System.out.println("Cargando "+ filename);
   }
   public void displayImage() { 
       System.out.println("Displaying "+ filename); 
   }
}
 
// Proxy
class ProxyImage implements Image {
   private String filename;
   private Image image;
   public ProxyImage(String filename) { 
       this.filename = filename; 
   }
   public void displayImage() {
       if (image == null) {
           image = new RealImage(filename); // load only on demand
       }
       image.displayImage();
   }
}
 
class Client {
   public static void main(String[] args) {
       List<Image> images = new ArrayList<Image>();
       images.add( new ProxyImage("HiRes_10MB_Photo1") );
       images.add( new ProxyImage("HiRes_10MB_Photo2") );
       images.add( new ProxyImage("HiRes_10MB_Photo3") );
 
       images.get(0).displayImage();
       images.get(1).displayImage();
       images.get(0).displayImage();
   }
}
 
/*
1. Se carga la primera imágen y se muestra.
2. Se carga la segunda imágen y se muesra.
3. Se muestra la primera imágen ya cargada.
4. La tercera imágen no llega a cargarse nunca.

Proxy de protección

   public interface IClient{
       string Login();
   }
 
   public class RealClient : IClient{
       public string Login(){
           return "Logged";
       }
   }
 
 
   public class ProtectionProxy : IClient 
   {
       private string password=null;
       RealClient client=null;
 
       public ProtectionProxy(string pwd)
       {
           Console.WriteLine("ProtectionProxy: Initialized");
           password=pwd;
       }
       public String Login() {
           if(tmpPwd == password) {
               client=new RealClient();
               return "Successfull!";
           } else
               return "Fail!";
       }
   }
 
   class ProtectionProxyExample {
       public static void Main(string[] args) {
           IClient client = new ProtectionProxy("thePassword");
           Console.WriteLine(client.Login());
       }
   }

Behavioral Patterns

Chain of Responsibility

Se basa en una serie de objetos por la que circula una petición realizada a un objeto inicial, tras recibirla este la pasa al siguiente objeto y así irá pasándose hasta llegar al objeto adecuado para procesarla. Todos estos objetos receptores derivarán de una misma interface o clase abstracta, que provea un método para obtener el sucesor, esto hará que la cadena sea más flexible y transparente.
Existen varias técnicas para desarrollar esta cadena:

UML

// Handler
public abstract class Empleado {
  Empleado sucesor;
  public void setSucesor (Empleado e) {
    this.sucesor = e;
  }
  public void ReceivePeticion (Peticion pet) {
    if (Condition(pet))
      Process (pet);
    else {
      if (this.sucesor != null)
        this.sucesor.ReceivePeticion(pet);
      else
        // Gestión de la petición si no existe sucesor
    } 
  }
  abstract void Process (Peticion pet);
  abstract boolean Condition (Peticion pet);
}
 
// Concrete Handler1
public class Director extends Empleado {
  boolean Condition (Peticion pet) {
    return (pet.dinero >= 5000);
  } 
  void Process (Peticion pet) {
    // Proceso de una petición para el director
  }
}
 
// Concrete Handler2
public class Gerente extends Empleado {
  boolean Condition (Peticion pet) {
    return (pet.dinero >= 2500);
  } 
  void Process (Peticion pet) {
    // Proceso de una petición para el gerente
  }
}
 
// Deben ser asignados los sucesores
Gerente ger = new Gerente();
Director dir = new Director();
dir.setSucesor(ger);

Command

Utiliza las acciones de los objetos como otros objetos, de esa forma nos permite clasificar las peticiones, registrarlas y hasta deshacerlas.

UML

interface Command {
  public Number Execute (Number n);
  public Number Undo (Number n);
}
 
// Receiver
class Number {
  int value = 0;
  public Number (int v) {
    this.value = v;
  }
  public int getValue () {
    return this.value;
  }
}
 
class Invoker {
  int idx = 0;
  Stack commands;
  Number vActual;
  public void Execute (char operation, Number n) {
    Command c;
    switch (operation) {
      case '+':
        c = new Suma (n);
        break;
      case '-':
        c = new Resta (n);
        break;
    }
    vActual = c.Execute();
    commands.Push(c);
    idx++;
  }
  public void Undo () {
    idx--;
    Command c = commands.Pop();
    vActual = c.Undo(vActual);
  }
}
 
// ConcreteCommand
public class Suma implements Command {
  Number value;
  public Suma (Number n) {
    this.value = n;
  }
  public Number Execute (Number n) {
    return new Number(this.value.getValue() + n.getValue());
  }
  public Number Undo (Number n) {
    return new Number(n.getValue() - this.value.getValue());
  }
}

Este patrón nos proporciona:

Interpreter

Su idea básica es la especificación e implementación un sub-lenguaje que resuelva rápidamente un tipo de problemas dinámicos.
Por ejemplo, desarrollar una clase interprete para expresiones matemáticas, esta recibiría en el constructor un String con la expresión a calcular y tendría un método “Solution” que procesaría dicha expresión y retornaría un resultado. Para procesar la expresión se ayudaría de clases que representasen los números, los signos (una para cada signo, realizando cada una la acción concreta con los objetos número)…

Iterator

Se basa en utilizar una interface que defina los métodos adecuados para realizar acciones sobre listas (recorrer secuencialmente y posicionarse en sus elementos (siguiente, anterior, primero, ultimo), añadirlos, eliminarlos…); todas las clases que correspondan a estas listas deberán implementar la interface, de esa forma todas las listas de objetos se tratarán igual aunque su estructura interna (pila, cola, hash list…) sea totalmente distinta.

Mediator

Define una comunicación simplificada entre clases, para ello en un objeto encapsula la forma en la que otros interactúan.
Cuando en un escenario un gran número de clases interactúan entre sí, haciendo que unas cambien el estado de las otras, puede llegarse a una estructura muy compleja. Además, la reutilización y personalización de los objetos se complica debido a que existe una gran interdependencia entre ellos y es complejo separarlos del del grupo. Necesitamos un mediator!

UML

Existen varias formas de implementar un Mediator:

Memento

Captura externamente el estado de un objeto para luego restaurarlo, y esto sin violar las reglas de encapsulación. Se almacenará el estado en una clase independiente a la del objeto a restaurar. Implementaciones alternativas:

UML

Observer

Provee una forma de que un objeto avise a otros que ha cambiado de estado, y así estos puedan actualizarse automáticamente y autónomamente, teniendo los dos tipos de objetos una relación nula (el que avisa no tiene por qué conocer a los receptores). El objeto que cambia de estado y avisa es el observado y los que reciben el aviso que ha cambiado los observadores.


UML

public abstract class Subject {
  LinkedList<Observer> listObservers;
  ...
  public void AddObserver (Observer) {...}
  public void DeleteObserver (Observer) {...}
  public Notify () {
    for (Observer o : listObservers)
      o.Update();
  }
}
 
public class Pelota extends Subject {
  Point point;
  ...
  void Mover (int x, int y) {
    point = new Point (x, y);
    this.Notify();
  }
}
 
public class Espectador implements Observer {
  Pelota p;
  ...
  void Update () {
    MirarPosicion(p.getPoint());
  } 
}

State

Cambia el comportamiento del objeto cuando su estado cambia. Para lograrlo crea un objeto por cada estado, codificando los métodos adecuados para cada estado concreto, el objeto que cambia de estado almacena una instancia a este tipo de objeto estado y actuará según le dicte este objeto. UML

public interface State {
  public void setVelocity (int vel);
  public int getGasto ();
}
 
public class stateStop implements State {
  private Car the_car;
  public stateStop (Car c) {
    this.the_car = c;
  }
  public void setVelocity (int vel) {
    if (vel > 0)
      the_car.setState(new stateRun());
  }
  public int getGasto () {
    return 0;
  }
}
 
public class stateRun implements State {
  private Car the_car;
  public stateRun (Car c) {
    this.the_car = c;
  }
  public void setVelocity (int vel) {
    if (vel <= 0)
      the_car.setState(new stateStop());
  }
  public int getGasto () {
    return 1;
  }
}
 
public class Car {
  State state;  
  int gasolina = 100;
  public Car () {
    state = new stateStop(this);
  }
  public void setState(State s) {
    this.state = s;
  }
  // A este método se llama una vez por minuto.
  public void gastar () {
    this.gasolina -= this.state.getGasto();
  }
}

En este ejemplo se muestra una de las dos posibles implementaciones para el cambio de estado, el estado es quien elige cual será el siguiente cambio de estado. Otra forma de realizar la transición entre estados podría ser la implementación en la misma clase Context.

Strategy

Se basa en la óptima utilización de métodos abstractos y de la implementación de la herencia de las clases que los contienen.
Una clase (A) con un método abstracto, el ejecutor de la estrategia, define varias estratégias según las clases (E1, E2, E3…) que heredan de esta e implementan dicho método. Otra clase (B) que quiera utilizar una de estas estrategias definirá un objeto del primer tipo (A) como uno nuevo de sus herederos (E1, E2, E3…) y llamará al método de la estrategia.

// E1, E2 y E3 heredan de A
A obj1 = new E1();
A obj2 = new E2();
A obj3 = new E3();
// A contiene el método abstracto strategy que E1, E2 y E3 han implementado
obj1.strategy();
obj2.strategy();
obj3.strategy();

Template Method

En una clase abstracta que contiene un método para realizar un algoritmo y este, a su vez, utiliza otros métodos abstractos de esa clase para realizar dicho algoritmo; en las clases que hereden de esta se podrán sobreescribir estos métodos (o deberán si se implementan como abstractos) para cambiar el algoritmo. UML
Los pasos para implementar el TemplateMethod son los métodos PrimitiveOperation1 y PrimitiveOperation2, el algoritmo cambiará una vez los cambiemos.

Visitor

Permite una nueva operación a una clase sin efectuar ningún cambio en esta.
Existe un objeto (ObjectStructure en el UML) que contiene una colección de elementos visitables (Element en el UML), a este se le pasa un objeto visitador (en el UML el visitador es un objeto ConcretVisitor que hereda de Visitor). El objeto al que le ha llegado el visitador recorre todos los elementos de la lista y se los pasa al visitador para que realice su visita (en el UML esta corresponde al método VisitConcretElement). Una visista consiste en la nueva operación que queremos implementar sobre los objetos visitables.
También puede ser implementado mediante un patrón composite, ya que para implementar este patrón añadiendo nueva funcionalidad a la estructura esta no ha de sufrir cambio alguno para poder realizar las visitas.

UML

// Interfaces
public interface Visitor {
     public void visit (Visitable v);
}
public interface Visitable {
     public void accept (Visitor visitador);
}
 
// Element
public abstract class GeometricRegularFigure implements Visitable {
  protected int nLados;
  protected int sizeLado;
  public void accept (Visitor visitador) {
     visitador.visit(this);
  }
  public int getNLados() {
     return nLados;
  }
  public int getSizeLado() {
     return sizeLado;
  }
}
 
// ConcreteElement1 (Visitable)
public class Square extends GeometricRegularFigure {
  public Square (int size) {
     this.nLados = 4;
     this.sizeLado = size;
  }
}
 
// ConcreteElement2 (Visitable)
public class Triangle extends GeometricRegularFigure{
  public Triangle (int size) {
     this.sizeLado = size;
     this.nLados = 3;
  }
}
 
// ObjectStructure
public class ContainerFigures {
  private ArrayList<GeometricRegularFigure> figures;
  public ContainerFigures() {
     this.figures = new ArrayList<GeometricRegularFigure>();
  }
  public void Add (GeometricRegularFigure f) {
     this.figures.add(f);
  }
  public void realizaVisitas (Visitor visitador) {
     for (GeometricRegularFigure grf : figures)
        visitador.visit(grf);
  }
}
 
// Visitador
public class PerimeterCalculator implements Visitor {
  private int perimeter;
  public PerimeterCalculator() {
     this.perimeter = 0;
  }
  public void visit(Visitable v) {
     this.perimeter += (((GeometricRegularFigure)v).getNLados() * ((GeometricRegularFigure)v).getSizeLado());
  }
  public int getCalculo () {
     return this.perimeter;
  }
}
 
// Cliente
ContainerFigures coleccion = new ContainerFigures();
coleccion.Add(new visitorATD.Triangle(5));
coleccion.Add(new visitorATD.Triangle(4));
coleccion.Add(new visitorATD.Square(15));
coleccion.Add(new visitorATD.Square(5));
 
PerimeterCalculator pc = new PerimeterCalculator();
coleccion.realizaVisitas(pc);

Documentos

designpatternscard.pdf