Design Patterns - State

Propósito

El patrón State permite a un objeto alterar su comportamiento cuando su estado interno cambia.

El patrón encapsula los estados variables como objetos de estado independientes, separando las preocupaciones del estado y el comportamiento.

Ante un cambio de estado, el objeto parecerá cambiar su clase.

Problema

El patrón State trata los siguientes problemas:

Comportamiento Basado en el Estado: Maneja situaciones donde el comportamiento de un objeto depende de su estado, y debe cambiar en tiempo de ejecución.

Condiciones Complejas: Evita el uso de múltiples condicionales para determinar el comportamiento de un objeto basado en su estado.

Solución

El patrón State propone las siguientes soluciones:

Objetos de Estado: Encapsula los diferentes estados de un objeto en clases de estado separadas, cada una implementando un comportamiento específico.

Contexto: Un objeto que mantiene una referencia a un objeto de estado que representa su estado actual. El contexto delega el comportamiento relacionado con el estado a los objetos de estado.

Estructura

state

Participantes

  • Context: Tiene un estado que cambia. Mantiene una referencia al estado actual y delega las tareas al estado.
  • State (Interface): Define una interfaz para encapsular el comportamiento asociado con un estado particular del Context.
  • ConcreteState: Implementa la interfaz State y define el comportamiento para un estado específico del Context.

Cuándo Usarlo

Este patrón es recomendable cuando:

  • el comportamiento de un objeto debe cambiar según su estado y son identificables los estados.
  • hay un número grande de operaciones condicionales en el código que selecciona un comportamiento dependiendo del estado del objeto.

Ventajas

Separación de Responsabilidades: Separa el código relacionado con el estado del objeto, del resto del comportamiento del objeto.

Organización y Mantenibilidad: Facilita la organización del código y su mantenimiento, especialmente en sistemas complejos.

Flexibilidad para Cambios: Permite cambiar fácilmente el comportamiento del objeto agregando nuevos estados.

Desventajas

Aumento de la Complejidad: Agrega varias clases y objetos adicionales.

Número de Clases: Cuánto más estados, más clases, por lo que pueden crecer considerablemente.

Dependencia de Contexto y Estado: La comunicación entre el contexto y los objetos de estado puede volverse compleja.

Ejemplo: Sistema de Control de Semáforos

Debemos crear un sistema de control de semáforos, algo crítico para regular el tráfico. Deben manejar varios estados (Rojo, Amarillo, Verde) de manera eficiente y fiable.

Problema

El desafío está en gestionar los cambios de estado del semáforo de una manera que sea fácil de entender, mantener y expandir.

Un semáforo no solo debe cambiar entre los estados de Rojo, Amarillo y Verde, sino también hacerlo de manera que respete las reglas de tráfico y seguridad.

Implementar esta lógica de cambio de estado directamente dentro de la clase del semáforo puede llevar a un código complejo y difícil de mantener (lleno de condiciones).

Solución planteada

Decidimos usar el patrón State porque nos permite encapsular los comportamientos asociados con los diferentes estados del semáforo en clases separadas.

Esto simplifica la lógica en la clase principal del semáforo y hace que el sistema sea más flexible y fácil de modificar o expandir.

Por ejemplo, si las reglas de tráfico cambian o se introducen nuevos tipos de semáforos, podemos agregar o modificar los estados correspondientes sin alterar el núcleo de la lógica del semáforo.

Definimos la interfaz TrafficLightState para manejar los estados del semáforo. Creamos los estados concretos (Red, Yellow, Green), y el Context que mantiene el estado actual y delega los cambios de estado al State. Finalmente, en el cliente vamos cambiando de estado cada cierto tiempo, simulando un semáforo.

state

Código Java

Codificamos en Java lo que preparamos en el diagrama.

Definimos la interfaz State:


              
  interface TrafficLightState {
      void handleState(TrafficLightContext context);
  }
              
              

Creamos los concrete States:


              
  class GreenState implements TrafficLightState {
      public void handleState(TrafficLightContext context) {
          System.out.println("Luz verde: El tráfico puede avanzar.");
          context.setState(new YellowState());
      }
  }
  
  class YellowState implements TrafficLightState {
      public void handleState(TrafficLightContext context) {
          System.out.println("Luz amarilla: Precaución.");
          context.setState(new RedState());
      }
  }
  
  class RedState implements TrafficLightState {
      public void handleState(TrafficLightContext context) {
          System.out.println("Luz roja: Detener el tráfico.");
          context.setState(new GreenState());
      }
  }
              
              

Implementamos el Context:


              
  class TrafficLightContext {
      private TrafficLightState state;
  
      public TrafficLightContext() {
          state = new RedState();  // Estado inicial
      }
  
      void setState(TrafficLightState state) {
          this.state = state;
      }
  
      void change() {
          state.handleState(this);
      }
  }
              
              

El código cliente:


              
  public class TrafficManagementSystem {
      public static void main(String[] args) {
          TrafficLightContext trafficLight = new TrafficLightContext();
  
          for (int i = 0; i < 6; i++) {
              trafficLight.change();
              try {
                  Thread.sleep(2000);  // Simular tiempo de espera entre cambios
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
  }
              
              

Mapeo (del ejemplo a Participantes)

Los participantes que vimos antes son: Context, State, ConcreteState, Client:

  • TrafficLightState (State): Interfaz para encapsular el comportamiento asociado con un estado del semáforo.
  • GreenState, YellowState, RedState (ConcreteState): Implementaciones concretas de los diferentes estados del semáforo.
  • TrafficLightContext (Context): Mantiene la referencia al estado actual del semáforo y delega la lógica de cambio de estado.
  • TrafficManagementSystem (Client): Usa el Context para simular el cambio de estados en un semáforo.

Conclusiones

En este ejemplo de uso del patrón State pudimos ver una gestión flexible de los diferentes estados de luz del semáforo, simplificando cambios y ampliaciones futuras.

Aunque incrementó el número de clases y podría introducir complejidad en sistemas más simples, eliminó la necesidad de condicionales complicados e hizo que el código sea fácil de mantener.

Patrones relacionados

  • Strategy

Strategy permite cambiar el comportamiento del objeto dinámicamente, State permite cambiar el comportamiento de acuerdo a su estado interno.