Command Palette

Search for a command to run...

State Pattern: Gestión de Comportamiento Dinámico

Aprende a cambiar el comportamiento de un objeto según su estado interno usando el State Pattern en JavaScript

Lectura: 15 min
Nivel: Intermedio

TL;DR - Resumen rápido

  • El State Pattern permite que un objeto cambie su comportamiento según su estado interno
  • Cada estado se implementa como una clase u objeto separado con su propia lógica
  • Las transiciones entre estados se manejan de forma centralizada y predecible
  • Elimina condicionales complejos (if/else o switch) al delegar comportamiento a estados
  • Ideal para objetos con múltiples estados y transiciones bien definidas

Introducción al State Pattern

El State Pattern es un patrón de diseño comportamental que permite a un objeto alterar su comportamiento cuando su estado interno cambia. El objeto parecerá cambiar de clase, pero en realidad está delegando el comportamiento a objetos de estado diferentes. Esto es especialmente útil cuando tienes un objeto con múltiples estados y el comportamiento varía significativamente según el estado actual.

En JavaScript, el State Pattern se puede implementar de varias formas: usando clases ES6, objetos literales o incluso funciones. La idea clave es encapsular cada estado en un objeto separado que define cómo debe comportarse el objeto en ese estado específico. El contexto (el objeto principal) mantiene una referencia al estado actual y delega las operaciones a ese estado.

¿Cuándo usar el State Pattern?

El State Pattern es ideal cuando tienes un objeto con múltiples estados bien definidos y el comportamiento varía según el estado. Ejemplos comunes incluyen: estados de un pedido (pendiente, procesando, completado, cancelado), estados de un documento (borrador, revisión, publicado), o estados de una conexión (conectado, desconectado, reconectando).

Implementación Básica del State Pattern

La implementación básica del State Pattern requiere tres componentes principales: el contexto que mantiene el estado actual, la interfaz de estado que define los métodos comunes, y las implementaciones concretas de cada estado. En JavaScript, podemos usar clases ES6 o objetos literales para implementar estos componentes.

Máquina de Estados

Una máquina de estados es la representación formal de los posibles estados de un objeto y las transiciones entre ellos. Cada estado define qué acciones son posibles y bajo qué condiciones puede transicionar a otro estado. Esto hace el comportamiento predecible y fácil de entender.

state-maquina.js
Loading code...

Este ejemplo muestra una implementación básica del State Pattern para un sistema de pedidos. Cada estado (Pendiente, Procesando, Completado, Cancelado) define su propio comportamiento para las operaciones disponibles. El contexto (Pedido) mantiene el estado actual y delega las operaciones a ese estado, eliminando la necesidad de condicionales complejos.

Ventajas del State Pattern

El State Pattern elimina condicionales complejos y hace el código más mantenible. Cada estado es una clase independiente, lo que facilita agregar nuevos estados sin modificar el código existente. Además, las transiciones de estado son explícitas y controladas, lo que reduce la posibilidad de estados inválidos.

Transiciones de Estado

Las transiciones de estado definen cómo un objeto pasa de un estado a otro. Es importante que las transiciones sean controladas y predecibles para evitar estados inválidos. Cada estado puede definir qué transiciones son posibles y bajo qué condiciones pueden ocurrir.

state-transiciones.js
Loading code...

Este ejemplo muestra cómo implementar transiciones controladas entre estados. Cada estado define qué transiciones son posibles y lanza un error si se intenta una transición inválida. Esto garantiza que el objeto siempre esté en un estado válido y que las transiciones sigan las reglas de negocio definidas.

Contexto del Estado

El contexto es el objeto principal que mantiene el estado actual y proporciona una interfaz para interactuar con él. El contexto delega las operaciones al estado actual y puede mantener información adicional que es compartida entre todos los estados.

state-contexto.js
Loading code...

Este ejemplo muestra cómo el contexto puede mantener información adicional que es compartida entre todos los estados. El contexto proporciona métodos para cambiar el estado y delega las operaciones al estado actual. Esto permite que los estados accedan a información compartida sin necesidad de pasarla explícitamente en cada llamada.

Casos de Uso del State Pattern

El State Pattern tiene múltiples aplicaciones prácticas en desarrollo de software. Comprender estos casos de uso te ayudará a identificar cuándo es apropiado implementar este patrón en tus proyectos.

  • <strong>Gestión de pedidos:</strong> Estados como pendiente, procesando, enviado, entregado o cancelado.
  • <strong>Documentos y contenido:</strong> Estados como borrador, revisión, publicado, archivado.
  • <strong>Conexiones de red:</strong> Estados como conectado, desconectado, reconectando, error.
  • <strong>Flujos de usuario:</strong> Estados como autenticando, autenticado, expirado, bloqueado.
  • <strong>Procesos de trabajo:</strong> Estados como iniciado, en progreso, pausado, completado.
  • <strong>Juegos y animaciones:</strong> Estados como reposo, caminando, corriendo, saltando.

Errores Comunes

Al implementar el State Pattern, es fácil cometer errores que pueden causar comportamiento inesperado o problemas de mantenimiento. Aquí analizamos los errores más frecuentes y cómo evitarlos.

  • <strong>Estados inválidos:</strong> Siempre valida las transiciones para evitar estados inconsistentes
  • <strong>Estado compartido mutable:</strong> Mantén el estado compartido en el contexto, no en los estados
  • <strong>Olvidar actualizar estado:</strong> Asegúrate de que cada transición actualice el estado del contexto
  • <strong>Demasiados estados:</strong> Si tienes muchos estados, considera si realmente necesitas el patrón
  • <strong>No documentar transiciones:</strong> Documenta claramente qué transiciones son posibles desde cada estado

Permitir Estados Inválidos

Un error común es no validar las transiciones de estado, lo que puede llevar a estados inválidos. Es importante que cada estado defina qué transiciones son posibles y que el contexto valide las transiciones antes de cambiar el estado.

error-estado-invalido.js
Loading code...

Este ejemplo muestra cómo implementar validación de transiciones para evitar estados inválidos. Cada estado define qué transiciones son posibles y el contexto valida antes de cambiar el estado. Si se intenta una transición inválida, se lanza un error con un mensaje descriptivo.

Compartir Estado entre Estados

Otro error común es compartir estado mutable entre diferentes estados, lo que puede causar efectos secundarios inesperados. Cada estado debe ser independiente y no debe depender del estado de otros estados. El contexto es el lugar apropiado para mantener el estado compartido.

error-estado-compartido.js
Loading code...

Este ejemplo muestra el problema de compartir estado mutable entre estados. Cuando los estados modifican un objeto compartido, pueden causar efectos secundarios inesperados. La solución es mantener el estado compartido en el contexto y que cada estado opere sobre copias o valores inmutables.

Advertencia de Estado Compartido

Compartir estado mutable entre estados es una fuente común de bugs difíciles de depurar. Si necesitas compartir información entre estados, mantenla en el contexto y asegúrate de que cada estado no modifique directamente el estado de otros. Considera usar valores inmutables cuando sea posible.

Olvidar Actualizar el Estado

Es fácil olvidar actualizar el estado después de una transición, lo que puede dejar el objeto en un estado inconsistente. Es importante que cada transición de estado actualice explícitamente el estado del contexto y que todas las operaciones que causan transiciones lo hagan de forma consistente.

error-estado-olvido.js
Loading code...

Este ejemplo muestra cómo olvidar actualizar el estado puede causar comportamiento inesperado. Cuando una operación causa una transición de estado, es crucial actualizar el estado del contexto. Una buena práctica es encapsular las transiciones en métodos del contexto que garantizan que el estado se actualice correctamente.

Resumen: State Pattern

Conceptos principales:

  • El State Pattern permite cambiar el comportamiento según el estado interno del objeto
  • Cada estado se encapsula en una clase u objeto separado con su propia lógica
  • El contexto mantiene el estado actual y delega las operaciones a ese estado
  • Las transiciones de estado deben ser controladas y predecibles
  • Elimina condicionales complejos y mantiene estado compartido en el contexto

Mejores prácticas:

  • Define claramente qué transiciones son posibles desde cada estado
  • Valida las transiciones para evitar estados inválidos
  • Mantén el estado compartido en el contexto, no en los estados
  • Usa el State Pattern cuando tengas múltiples estados bien definidos
  • Implementa cada estado como una clase u objeto independiente
  • Documenta las transiciones y el comportamiento de cada estado