Patrones de Diseño

Cada patrón describe un problema que ocurre una y otra vez en nuestro entorno, para describir después el núcleo de la solución a ese problema, de tal manera que esa solución pueda ser usada más de un millón de veces sin hacerlo ni siquiera dos veces de la misma forma

Christopher Alexander
1977

Tipos de Patrones

  • De creación: Iniciación y configuración de clases y objetos
  • Estructurales: Composición de clases y objetos. Bajo acoplamiento entre clases.
  • De comportamiento: Comunicación o interacción entre objetos. Distribución de responsabilidades.

Gamma, E. (1995). Design patterns: elements of reusable object-oriented software. Pearson Education India.

Factory Method

Consiste en utilizar una clase constructora abstracta con unos cuantos métodos definidos y otro(s) abstracto(s): el dedicado a la construcción de objetos de un subtipo de un tipo determinado

Factory Method

Factory Method

abstract class Creator{
    // Definimos método abstracto
    public abstract Product factoryMethod()
}
public class ConcreteCreator extends Creator{
    public Product factoryMethod() {
        return new ConcreteProduct();
    }
}
public interface Product{
    public void operacion();
}

public class ConcreteProduct implements Product{
    public void operacion(){
        System.out.println("Una operación de este producto");
    }
}
public static void main(String args[]){
    Creator aCreator;
    aCreator = new ConcreteCreator();
    Product producto = aCreator.factoryMethod();
    producto.operacion();
}

Builder

Usado para permitir la creación de una variedad de objetos complejos desde un objeto fuente (Producto), el objeto fuente se compone de una variedad de partes que contribuyen individualmente a la creación de cada objeto complejo

Builder

Builder

/** "Producto" */
class Pizza {
    private String masa = "";
    private String salsa = "";
    private String relleno = "";
 
    public void setMasa(String masa)     { this.masa = masa; }
    public void setSalsa(String salsa)     { this.salsa = salsa; }
    public void setRelleno(String relleno) { this.relleno = relleno; }
}
 
 
/** "Abstract Builder" */
abstract class PizzaBuilder {
    protected Pizza pizza;
 
    public Pizza getPizza() { return pizza; }
 
    public abstract void buildMasa();
    public abstract void buildSalsa();
    public abstract void buildRelleno();
}

 
/** "ConcreteBuilder" */
class HawaiPizzaBuilder extends PizzaBuilder {
    public HawaiPizzaBuilder(){super.pizza = new Pizza();}
    public void buildMasa()   { pizza.setMasa("suave"); }
    public void buildSalsa()   { pizza.setSalsa("dulce"); }
    public void buildRelleno() { pizza.setRelleno("chorizo+alcachofas"); }
}
 
/** "ConcreteBuilder" */
class PicantePizzaBuilder extends PizzaBuilder {
    public PicantePizzaBuilder(){super.pizza = new Pizza();}
    public void buildMasa()   { pizza.setMasa("cocida"); }
    public void buildSalsa()   { pizza.setSalsa("picante"); }
    public void buildRelleno() { pizza.setRelleno("pimienta+salchichón"); }
}
 
 
/** "Director" */
class Cocina {
    private PizzaBuilder pizzaBuilder;
 
    public void setPizzaBuilder(PizzaBuilder pb) { pizzaBuilder = pb; }
    public Pizza getPizza() { return pizzaBuilder.getPizza(); }
 
    public void construirPizza() {
       pizzaBuilder.buildMasa();
       pizzaBuilder.buildSalsa();
       pizzaBuilder.buildRelleno();
    }
}
 
 
/** Un cliente pidiendo una pizza. */
class BuilderExample {
    public static void main(String[] args) {
        Cocina cocina = new Cocina();
        PizzaBuilder hawai_pizzabuilder = new HawaiPizzaBuilder();
        PizzaBuilder picante_pizzabuilder = new PicantePizzaBuilder();
 
        cocina.setPizzaBuilder( hawai_pizzabuilder );
        cocina.construirPizza();
 
        Pizza pizza = cocina.getPizza();
    }
}



/** 
 *  2da opción para el abstract builder quizá más transparente para su uso.
 *  Dentro del crear se llaman los métodos build.
 *  Es válido siempre y cuando no se necesite alterar
 *  el orden del llamado a los "build's".
 */
abstract class OtroPizzaBuilder {
    protected Pizza pizza;
 
    public Pizza getPizza() { return pizza; }
    public void crearNuevaPizza() { 
           pizza = new Pizza(); 
           buildMasa();
           buildSalsa();
           buildRelleno();
    }
 
    public abstract void buildMasa();
    public abstract void buildSalsasasa();
    public abstract void buildRelleno();
}

/** "Director" */
class OtraCocina {
    private OtroPizzaBuilder pizzaBuilder;
 

 
    public void construirPizza() {
       pizzaBuilder.crearNuevaPizza();
       //notar que no se necesita llamar a cada build.
    }
}

Prototype

 Tiene como finalidad crear nuevos objetos clonando una instancia creada previamente

Prototype

Singleton

Garantiza la existencia de una única instancia para una clase y la creación de un mecanismo de acceso global a dicha instancia.

Singleton

public class Singleton {
    private static Singleton INSTANCE = null;

    // Private constructor suppresses 
    private Singleton(){}

    // creador sincronizado para protegerse de posibles problemas  multi-hilo
    // otra prueba para evitar instanciación múltiple 
    private synchronized static void createInstance() {
        if (INSTANCE == null) { 
            INSTANCE = new Singleton();
        }
    }

    public static Singleton getInstance() {
        if (INSTANCE == null) createInstance();
        return INSTANCE;
    }
}

Composite

Sirve para construir objetos complejos a partir de otros más simples y similares entre sí, gracias a la composición recursiva y a una estructura en forma de árbol.

Composite

Composite

import java.util.*;

public abstract class Componente
{
	protected String nombre;
	public Componente (String nombre)
	{
		this.nombre = nombre;
	}
	abstract public void agregar(Componente c);
	abstract public void eliminar(Componente c);
	abstract public void mostrar(int profundidad);
}
class Compuesto extends Componente
{
	private ArrayList<Componente> hijo = new ArrayList<Componente>();
	public Compuesto (String name)
	{
		super(name);
	}
	@Override
	public void agregar(Componente componente)
	{
		hijo.add(componente);
	}
	@Override
	public void eliminar(Componente componente)
	{
		hijo.remove(componente);
	}
	@Override
	public void mostrar(int profundidad)
	{
		System.out.println(nombre + " nivel: " + profundidad);
		for (int i = 0; i < hijo.size(); i++)
			hijo.get(i).mostrar(profundidad + 1);
	}
}
class Hoja extends Componente
{
	public Hoja (String nombre)
	{
		super(nombre);
	}
	public void agregar(Componente c)
	{
		System.out.println("no se puede agregar la hoja");
	}
	public void eliminar(Componente c)
	{
		System.out.println("no se puede quitar la hoja");
	}
	public void mostrar(int depth)
	{
		System.out.println('-' + "" + nombre);
	}
}
public class Client
{
	public static void main(String[] args)
	{
		Compuesto raiz = new Compuesto("root");
		raiz.agregar(new Hoja("hoja A"));
		raiz.agregar(new Hoja("hoja B"));
		Compuesto comp = new Compuesto("compuesto X");
		comp.agregar(new Hoja("hoja XA"));
		comp.agregar(new Hoja("hoja XB"));
		raiz.agregar(comp);
		raiz.agregar(new Hoja("hoja C"));
		Hoja l = new Hoja("hoja D");
		raiz.agregar(l);
		raiz.eliminar(l);
		raiz.mostrar(1);
	}
}

Façade

 Viene motivado por la necesidad de estructurar un entorno de programación y reducir su complejidad con la división en subsistemas, minimizando las comunicaciones y dependencias entre estos.

Façade

Observable

 Define una dependencia del tipo uno a muchos entre objetos, de manera que cuando uno de los objetos cambia su estado, notifica este cambio a todos los dependientes.

Observable

Observable

import java.util.*;

class FuenteEvento extends Observable implements Runnable {
    public void run() {
        while (true) {
            String respuesta = new Scanner(System.in).next();
            setChanged();
            notifyObservers(respuesta);
        }
    }
}
import java.util.Observable;
import static java.lang.System.out;

class MiApp {
    public static void main(String[] args) {
        out.println("Introducir Texto >");
        FuenteEvento fuenteEvento = new FuenteEvento();

        fuenteEvento.addObserver( (Observable obj, Object arg) -> {
            out.println("\nRespuesta recibida: " + arg);
        });

        new Thread(fuenteEvento).start();
    }
}

Strategy

 Permite mantener un conjunto de algoritmos de entre los cuales el objeto cliente puede elegir aquel que le conviene e intercambiarlo dinámicamente (tiempo de ejecución) según sus necesidades

Strategy

Strategy

public class Main {	
	public static void main(String args[])
	{
		//Usamos la estrategia A
		Strategy estrategia_inicial = new StrategyA();
		Context context = new Context(estrategia_inicial);
		context.some_method();
		
		//Decidimos usar la estrategia B
		Strategy estrategia2 = new StrategyB();
		context.setStrategy(estrategia2);
		context.some_method();
		
		//Finalmente,usamos de nuevo la estrategia A
		context.setStrategy(estrategia_inicial);
		context.some_method();
		
		/** Salida:
		 * Estrategia A
		 * Estrategia B
		 * Estrategia A
		 **/
	}
}


public class Context {
	Strategy c;

	public Context( Strategy c )
	{
		this.c = c;
	}

	public void setStrategy(Strategy c) {
		this.c = c;
	}
	
	//Método de estrategia 'c'
	public void some_method()
	{
		c.behaviour();
	}
}

public Interface Strategy{
       public void behaviour();
} 

public class StrategyA implements Strategy{
	@Override
	public void behaviour() {
		System.out.println("Estrategia A");
	}
}

public class StrategyB implements Strategy{
	@Override
	public void behaviour() {
		System.out.println("Estrategia B");
	}
}

Chain of Responsibility

Evita acoplar el emisor de una petición a su receptor dando a más de un objeto la posibilidad de responder a una petición. Para ello, se encadenan los receptores y pasa la petición a través de la cadena hasta que es procesada por algún objeto.

Chain of Responsibility

Evita acoplar el emisor de una petición a su receptor dando a más de un objeto la posibilidad de responder a una petición. Para ello, se encadenan los receptores y pasa la petición a través de la cadena hasta que es procesada por algún objeto.

Chain of Responsibility

By Martin.reigosa [CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0)], from Wikimedia Commons

Chain of Responsibility

public class Cliente {

    public static void main(String argv[]) {
      Unidad smith  = new Coronel("Smith", null);
      Unidad truman = new Coronel("Truman", "Tomar posición enemiga");
      Unidad ryan   = new Soldado("Ryan");
      Unidad rambo  = new Soldado("Rambo");

      System.out.println(rambo.orden());    // rambo ->

      rambo.establecerMando(truman);
      System.out.println(rambo.orden());    // rambo -> truman

      ryan.establecerMando(rambo);
      System.out.println(ryan.orden());     // ryan -> rambo -> truman
    }
}
/**
 * La clase Unidad representa la clase abstracta manejadora de la cadena de responsabilidad.
 * El servicio delegado en la cadena es la solicitud de una orden al mando directo
 */

public abstract class Unidad {

    /* en el constructor, además de un nombre para la unidad, se inicializa la referencia
       que implementa la cadena de responsabilidad (_mando): en principio no hay sucesor */

    public Unidad(String nombre) { 
        _mando = null; 
        _nombre = nombre;
    }

    public String toString() { return _nombre; }

    // cambia el mando de una unidad (modifica cadena de responsabilidad)

    public void establecerMando(Unidad mando) { _mando = mando; }

    /* comportamiento por defecto de la cadena: delegar en el mando directo o, si se 
       alcanza el final de la cadena, utilizar una resolución por defecto (sin orden) */

    public String orden() {
        return (_mando != null ? _mando.orden() : "(sin orden)"); 
    }

    private Unidad _mando;
    private String _nombre;
}

Chain of Responsibility

/**
 * La clase Coronel modifica ligeramente el comportamiento por defecto de la cadena de
 * responsabilidad: si el coronel tiene una orden específica, utiliza ésta para resolver
 * el servicio. Si no tiene una orden específica (_orden==null), emplea el comportamiento
 * convencional de las unidades
 */

public class Coronel extends Unidad {

    // inicializa la parte de unidad e inicializa el estado propio del Coronel (_orden)

    public Coronel(String nombre, String orden) {
      super(nombre);
      _orden = orden;
    }

    /* refinamiento del servicio que utiliza la cadena de responsabilidad, resolviendo
       localmente si tiene órdenes específicas o comportándose convencionalmente en
       caso contrario */

    public String orden()    { return (_orden != null ? _orden : super.orden()); }

    public String toString() { return ("Coronel " + super.toString()); }

    private String _orden;
}
/**
 * Esta clase es una extensión instanciable de la superclase Unidad que respeta el
 * comportamiento por defecto de la cadena de responsabilidad
 */

public class Soldado extends Unidad {

    // el constructor sólo tiene que inicializar la parte correspondiente a la superclase

    public Soldado(String nombre) {
        super(nombre);
    }
    
    public String toString() { return ("Soldado " + super.toString()); }
}

Template Method

 Define el esqueleto de un algoritmo en un método, llamado método de plantilla, el cual difiere algunos pasos a las subclases
 

Template Method

Template Method

/**
 * An abstract class that is common to several games in
 * which players play against the others, but only one is
 * playing at a given time.
 */

abstract class Game {
 /* Hook methods. Concrete implementation may differ in each subclass*/
    protected int playersCount;
    abstract void initializeGame();
    abstract void makePlay(int player);
    abstract boolean endOfGame();
    abstract void printWinner();

    /* A template method : */
    public final void playOneGame(int playersCount) {
        this.playersCount = playersCount;
        initializeGame();
        int j = 0;
        while (!endOfGame()) {
            makePlay(j);
            j = (j + 1) % playersCount;
        }
        printWinner();
    }
}

//Now we can extend this class in order 
//to implement actual games:

class Monopoly extends Game {

    /* Implementation of necessary concrete methods */
    void initializeGame() {
        // Initialize players
        // Initialize money
    }
    void makePlay(int player) {
        // Process one turn of player
    }
    boolean endOfGame() {
        // Return true if game is over 
        // according to Monopoly rules
    }
    void printWinner() {
        // Display who won
    }
    /* Specific declarations for the Monopoly game. */

    // ...
}

class Chess extends Game {

    /* Implementation of necessary concrete methods */
    void initializeGame() {
        // Initialize players
        // Put the pieces on the board
    }
    void makePlay(int player) {
        // Process a turn for the player
    }
    boolean endOfGame() {
        // Return true if in Checkmate or 
        // Stalemate has been reached
    }
    void printWinner() {
        // Display the winning player
    }
    /* Specific declarations for the chess game. */

    // ...
}

Gamma, E. (1995). Design patterns: elements of reusable object-oriented software. Pearson Education India.

 

Patrones de diseño

By Gustavo Andrés Uribe Gómez