Patrones de Diseño de Comportamiento

Un patrón de diseño es una estructura general que puede ser usada para solucionar un problema. No se trata ni de una librería ni de un esqueleto. Para que una solución se considere un patrón de diseño debe ser efectiva y reutilizable, es decir, que pueda aplicarse y resolver problemas similares.

Los Patrones de Comportamiento tratan con algoritmos y la asignación de responsabilidades entre objetos.

Chain of Responsibility (Cadena de responsabilidad, CoR, Chain of Command)

Es un patrón de diseño de comportamiento que te permite pasar solicitudes a lo largo de una cadena de hanlders. Este patrón es el que sigue el Middleware de las aplicaciones web por ejemplo, validación de formularios o en la propagación de eventos en una aplicación GUI. Cada receiver contiene una referencia a otro receiver. Si uno de los objetos no es capaz de manehar el request entonces lo pasa al siguiente.
public abstract class AbstractLogger {
   public static int INFO = 1;
   public static int DEBUG = 2;
   public static int ERROR = 3;

   protected int level;

   //next element in chain or responsibility
   protected AbstractLogger nextLogger;

   public void setNextLogger(AbstractLogger nextLogger){
      this.nextLogger = nextLogger;
   }
   public void logMessage(int level, String message){
      if(this.level <= level){
         write(message);
      }
      if(nextLogger !=null){
         nextLogger.logMessage(level, message);
      }
   }

   abstract protected void write(String message);
}
public class ConsoleLogger extends AbstractLogger {

   public ConsoleLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {		
      System.out.println("Standard Console::Logger: " + message);
   }
}
public class ErrorLogger extends AbstractLogger {

   public ErrorLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {		
      System.out.println("Error Console::Logger: " + message);
   }
}
public class FileLogger extends AbstractLogger {

   public FileLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {		
      System.out.println("File::Logger: " + message);
   }
}
public class ChainPatternDemo {
	
   private static AbstractLogger getChainOfLoggers(){

      AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
      AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
      AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);

      errorLogger.setNextLogger(fileLogger);
      fileLogger.setNextLogger(consoleLogger);

      return errorLogger;	
   }
   public static void main(String[] args) {
      AbstractLogger loggerChain = getChainOfLoggers();

      loggerChain.logMessage(AbstractLogger.INFO, 
         "This is an information.");

      loggerChain.logMessage(AbstractLogger.DEBUG, 
         "This is an debug level information.");

      loggerChain.logMessage(AbstractLogger.ERROR, 
         "This is an error information.");
   }
}

Command (Comando, Orden, Action, Transaction)

Este patrón se utiliza para poner tareas en cola, realizar el seguimiento de tareas ejecutadas y llevar a cabo la operación "deshacer". La clave aquí es que una solicitud se convierte en un objeto independiente que contiene toda la información sobre la solicitud, después ya las tratamos como queramos.
En el siguiente ejemplo el interface Order actuara como el Command, y la clase Stock como Request.
public interface Order {
   void execute();
}
public class Stock {
	
   private String name = "ABC";
   private int quantity = 10;

   public void buy(){
      System.out.println("Stock [ Name: "+name+", 
         Quantity: " + quantity +" ] bought");
   }
   public void sell(){
      System.out.println("Stock [ Name: "+name+", 
         Quantity: " + quantity +" ] sold");
   }
}
/// Clases que implementan comandos
public class BuyStock implements Order {
   private Stock abcStock;

   public BuyStock(Stock abcStock){
      this.abcStock = abcStock;
   }
   public void execute() {
      abcStock.buy();
   }
}
public class SellStock implements Order {
   private Stock abcStock;

   public SellStock(Stock abcStock){
      this.abcStock = abcStock;
   }
   public void execute() {
      abcStock.sell();
   }
}
// Y el invocador de los comandos
public class Broker {
   private List<Order> orderList = new ArrayList<Order>(); 

   public void takeOrder(Order order){
      orderList.add(order);		
   }
   public void placeOrders(){
      for (Order order : orderList) {
         order.execute();
      }
      orderList.clear();
   }
}
// Ya podemos usar el invocador de comandos
public class CommandPatternDemo {
   public static void main(String[] args) {
      Stock abcStock = new Stock();

      BuyStock buyStockOrder = new BuyStock(abcStock);
      SellStock sellStockOrder = new SellStock(abcStock);

      Broker broker = new Broker();
      broker.takeOrder(buyStockOrder);
      broker.takeOrder(sellStockOrder);

      broker.placeOrders();
   }
}
Iterator (Iterador)

Super utilizado e implementado ya por todos los lenguajes. Permite recorrer elementos de una colección sin exponer su representación subyacente (lista, pila, árbol, etc.)
public interface Iterator {
   public boolean hasNext();
   public Object next();
}
public interface Container {
   public Iterator getIterator();
}
public class NameRepository implements Container {
   public String names[] = {"Robert" , "John" ,"Julie" , "Lora"};

   @Override
   public Iterator getIterator() {
      return new NameIterator();
   }

   private class NameIterator implements Iterator {

      int index;

      @Override
      public boolean hasNext() {      
         if(index < names.length){
            return true;
         }
         return false;
      }
      @Override
      public Object next() {      
         if(this.hasNext()){
            return names[index++];
         }
         return null;
      }		
   }
}
public class IteratorPatternDemo {
	
   public static void main(String[] args) {
      NameRepository namesRepository = new NameRepository();
      for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){
         String name = (String)iter.next();
         System.out.println("Name : " + name);
      } 	
   }
}
Mediator (Mediador, Intermediary, Controller)

Este patrón se utiliza para reducir la complejidad de comunicación entre distintos objetos. Este Mediador maneja todas las comunicaciones entre diferentes clases, reduciendo el acoplamiento entre ellas.
Memento (Recuerdo, Instantánea, Snapshot)

Memento permite guardar y restaurar el estado previo de un objeto sin revelar los detalles de su implementación. Usa 3 classes principalmente: Memento que contiene el estado del objeto a ser restaurado, Originator que es quien crea y almacena el estado en los objetos Memento, y el objeto Caretaker que es el responsable de restaurar el estado del objeto.
public class Memento {
   private String state;

   public Memento(String state){
      this.state = state;
   }
   public String getState(){
      return state;
   }	
}
public class Originator {
   private String state;

   public void setState(String state){
      this.state = state;
   }
   public String getState(){
      return state;
   }
   public Memento saveStateToMemento(){
      return new Memento(state);
   }
   public void getStateFromMemento(Memento memento){
      state = memento.getState();
   }
}
public class CareTaker {
   private List<Memento> mementoList = new ArrayList<Memento>();

   public void add(Memento state){
      mementoList.add(state);
   }
   public Memento get(int index){
      return mementoList.get(index);
   }
}
// Demo
public class MementoPatternDemo {
   public static void main(String[] args) {
   
      Originator originator = new Originator();
      CareTaker careTaker = new CareTaker();
      
      originator.setState("State #1");
      originator.setState("State #2");
      careTaker.add(originator.saveStateToMemento());
      
      originator.setState("State #3");
      careTaker.add(originator.saveStateToMemento());
      
      originator.setState("State #4");
      System.out.println("Current State: " + originator.getState());		
      
      originator.getStateFromMemento(careTaker.get(0));
      System.out.println("First saved State: " + originator.getState());
      originator.getStateFromMemento(careTaker.get(1));
      System.out.println("Second saved State: " + originator.getState());
   }
}
Observer (Observador, Publicación-Suscripción, Modelo-patrón, Event-Subscriber, Listener)

Este patrón es muy popular y muchos frameworks lo incorporan. Implementa un mecanismo de suscripción para notificar a varios objetos sobre cualquier evento que le suceda al objeto que están observando. Se utiliza cuando se tiene una relación de comunicación one-to-many.
public class Subject {	
   private List<Observer> observers = new ArrayList<Observer>();
   private int state;

   public int getState() {
      return state;
   }
   public void setState(int state) {
      this.state = state;
      notifyAllObservers();
   }
   public void attach(Observer observer){
      observers.add(observer);		
   }
   public void notifyAllObservers(){
      for (Observer observer : observers) {
         observer.update();
      }
   } 	
}
public abstract class Observer {
   protected Subject subject;
   public abstract void update();
}
// Observer classes
public class BinaryObserver extends Observer{
   public BinaryObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
   @Override
   public void update() {
      System.out.println( "Binary String: " + Integer.toBinaryString( subject.getState() ) ); 
   }
}
...  
// Pattern example
public class ObserverPatternDemo {
   public static void main(String[] args) {
      Subject subject = new Subject();

      new HexaObserver(subject);
      new OctalObserver(subject);
      new BinaryObserver(subject);

      System.out.println("First state change: 15");	
      subject.setState(15);
      System.out.println("Second state change: 10");	
      subject.setState(10);
   }
}
State (Estado)

State es un patrón que permite a un objeto alterar su comportamiento cuando su estado interno cambia. Creamos objetos que representan diferentes estados y un objeto Contexto cuyo comportamiento varía cuando le estado del objeto cambia.

Normalmente si necesitas estados creas unos enums y dentro del objeto los usas en un switch. Cuanto más estados tengas mas grande será el switch, y por otro lado si necesitas que las acciones a realizar en un determinado estado sean diferentes dependiendo de donde venga, el switch ya no es una solución. Convertimos el estado en una clase abstracta State.
public abstract class State {
    Player player;
    State(Player player) {
        this.player = player;
    }
    public abstract String onLock();
    public abstract String onPlay();
    public abstract String onNext();
    public abstract String onPrevious();
}
// Some states
public class LockedState extends State {
    LockedState(Player player) {
        super(player);
        player.setPlaying(false);
    }
    @Override
    public String onLock() {
        if (player.isPlaying()) {
            player.changeState(new ReadyState(player));
            return "Stop playing";
        } else {
            return "Locked...";
        }
    }
    @Override
    public String onPlay() {
        player.changeState(new ReadyState(player));
        return "Ready";
    }
    @Override
    public String onNext() {
        return "Locked...";
    }
    @Override
    public String onPrevious() {
        return "Locked...";
    }
}
public class ReadyState extends State {
    public ReadyState(Player player) {
        super(player);
    }
    @Override
    public String onLock() {
        player.changeState(new LockedState(player));
        return "Locked...";
    }
    @Override
    public String onPlay() {
        String action = player.startPlayback();
        player.changeState(new PlayingState(player));
        return action;
    }
    @Override
    public String onNext() {
        return "Locked...";
    }
    @Override
    public String onPrevious() {
        return "Locked...";
    }
}
...
// Pattern example
public class Player {
    private State state;
    private boolean playing = false;
    private List<String> playlist = new ArrayList<>();
    private int currentTrack = 0;

    public Player() {
        this.state = new ReadyState(this);
        setPlaying(true);
        for (int i = 1; i <= 12; i++) {
            playlist.add("Track " + i);
        }
    }
    public void changeState(State state) {
        this.state = state;
    }
    public State getState() {
        return state;
    }
    public void setPlaying(boolean playing) {
        this.playing = playing;
    }
    public boolean isPlaying() {
        return playing;
    }
    public String startPlayback() {
        return "Playing " + playlist.get(currentTrack);
    }
    public String nextTrack() {
        currentTrack++;
        if (currentTrack > playlist.size() - 1) {
            currentTrack = 0;
        }
        return "Playing " + playlist.get(currentTrack);
    }
    public String previousTrack() {
        currentTrack--;
        if (currentTrack < 0) {
            currentTrack = playlist.size() - 1;
        }
        return "Playing " + playlist.get(currentTrack);
    }
    public void setCurrentTrackAfterStop() {
        this.currentTrack = 0;
    }
}
Strategy (Estrategia)

El patrón Strategy permite cambiar el comportamiento de una clase o uno de sus algoritmo en tiempo de ejecución. Se utiliza mucho en lenguajes como PHP o Java, no obstante, el patrón tiene un duro competidor representado por funciones anónimas en PHP o funciones Lambda en Java. Google Maps es un buen ejemplo ya que tiene distintos algoritmos para trazar rutas del punto A al B, dependiendo de si vas a pie, en coche o en bici.
public interface Strategy {
   public int doOperation(int num1, int num2);
}
// Algunas estrategias
public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}
public class OperationSubstract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}
public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}
// El objeto contezto
public class Context {
   private Strategy strategy;
   public Context(Strategy strategy){
      this.strategy = strategy;
   }
   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}
// Pattern example
public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());		
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

      context = new Context(new OperationSubstract());		
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

      context = new Context(new OperationMultiply());		
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}
Template Method (Método plantilla)

En este patrón una clase abstracta dispone distintas formas de ejecutar sus métodos. Serán las subclases las que sobreescriban su implementación, aunque su invocación será la definida en la clase abstacta. Un ejemplo claro es un algoritmo de minería de datos que analiza documentos PDF, DOC y CSV, y ésta intenta extraer la información relevante de estos documentos en un formato uniforme.
// La clase abstracta
public abstract class Game {
   abstract void initialize();
   abstract void startPlay();
   abstract void endPlay();

   //template method
   public final void play(){
      //initialize the game
      initialize();
      //start game
      startPlay();
      //end game
      endPlay();
   }
}
// Las diferentes implementaciones
public class Cricket extends Game {
   @Override
   void endPlay() {
      System.out.println("Cricket Game Finished!");
   }
   @Override
   void initialize() {
      System.out.println("Cricket Game Initialized! Start playing.");
   }
   @Override
   void startPlay() {
      System.out.println("Cricket Game Started. Enjoy the game!");
   }
}
public class Football extends Game {
   @Override
   void endPlay() {
      System.out.println("Football Game Finished!");
   }
   @Override
   void initialize() {
      System.out.println("Football Game Initialized! Start playing.");
   }
   @Override
   void startPlay() {
      System.out.println("Football Game Started. Enjoy the game!");
   }
}
// Pattern example
public class TemplatePatternDemo {
   public static void main(String[] args) {
      Game game = new Cricket();
      game.play();
      System.out.println();
      game = new Football();
      game.play();		
   }
}

Comentarios

Entradas populares de este blog

Envío de checkboxes o selector multiple por AJAX con jQuery

Patrones de Diseño Creacionales

Conceptos básicos de la POO