Patrón Estado

¿Alguna vez ha usado varios IF o SWITCH dentro de su aplicación para determinar que acción tomar? Por ejemplo, si el estado de la factura es nuevo, entonces, ejecute el método ABC, si es modificado entonces, ejecute DEF. ¿Qué pasa si necesitamos definir más acciones para un nuevo estado? ¿Cuantos IF tendría que modificar o agregar? Si reconoce este escenario, es posible que pueda implementar el patrón Estado.

Nuestra aplicación de ejemplo se encarga de manejar las posibles acciones de una cotización hasta que se convierte en una factura. Veamos el flujo de los estados de la cotización.

Diagrama de ejemplo de flujo de estados
  • In Progress: nos indica que se ha creado una cotización. La idea es que el vendedor realice los cambios necesarios, como agregar descuentos, productos, servicios, etc.
  • Pending: en este paso, la cotización está lista para ser revisada por un supervisor, cuando el supervisor da el visto bueno, el sistema realiza las validaciones necesarias y aprueba o rechaza la cotización.
  • Rejected: indica que la cotización no cumplió con alguna validación, en nuestro caso de ejemplo, la única validación va a ser existencias de los productos. Una vez en este estado, el vendedor debe cambiar la cotización, en este punto el sistema cambia el estado a In Progress.
  • Approved: la cotización fue verificada y validada en el sistema y esta lista para ser facturada. En este paso, por alguna excepción, como por ejemplo, que el cliente ya no desee la cotización o ésta se encuentra vencida, el sistema cambia el estado a Rejected.
  • Closed: sólo las cotizaciones que se materializaron, es decir, se lograron facturar, tienen este estado.

En nuestro primer diseño, podríamos crear un método dentro de la clase Cotización que se encargue de manejar la lógica para cada estado. Lo cual generaría una serie de IF o SWITCH dentro del método, veamos un ejemplo con varios IF.

public void nextStep() {
    if(this.getCurrentStep() == StepsEnum.IN_PROGRESS) {
        // code here
    }
    else if(this.getCurrentStep() == StepsEnum.PENDING) {
        // code here
    }
    else if(this.getCurrentStep() == StepsEnum.REJECTED) {
        // code here
    }
    // next if statements
}

Este sería el ejemplo pero utilizando un SWITCH.

public void nextStep() {
    switch(StepsEnum) {
        case IN_PROGRESS:
            // code here
        break;
        case PENDING:
            // code here
        break;
        case REJECTED:
            // code here
        break;
    }
    // next case statements
}

Si bien es cierto, el uso de un SWITCH es mejor que varios IF, sin embargo este diseño no es el más adecuado. Si el día de mañana introducen un nuevo estado a nuestro flujo, tendríamos que agregar un caso o un IF más.

Además, ¿qué pasa si también nos piden nuevas acciones/validaciones para cada caso específico? ¿Cuántos métodos tendríamos que modificar? ¿Cuánto código podríamos tener duplicado?

En resumen, iríamos en contra de todos los principios de diseño que hemos visto hasta el día de hoy.

Para resolver esto, podemos hacer uso del patrón de diseño llamado Estado.

El libro Head First Design Patterns define este patrón de la siguiente manera: El patrón Estado, le permite a un objeto modificar su comportamiento cuando su estado interno cambia. El objeto aparenta cambiar de clase.

Nuestra clase cotización en realidad se comporta de diferentes maneras según su estado interno, es decir, aparenta ser otra clase por medio de sus estados.

Este sería el diagrama de clases sugerido para este patrón.

Diagrama de clases sugerido para el patrón estado

Al implementar el patrón Estado, es normal tener que crear varias clases. Podríamos decir que cada estado que logremos identificar, va a ser materializado en una clase.

El diagrama de clases de nuestro ejemplo es el siguiente.

Diagrama de clases del ejemplo implementando el patrón estado

En el diagrama de clases anterior, creamos una interfaz de la cual van a heredar todos los estados que logramos identificar. También es necesario crear una clase para cada estado, las cuáles contienen la lógica específica para el estado actual.

Es decir, estamos aplicando el principio de diseño que nos indica que una clase debe tener sólo una responsabilidad.

El resultado de ejecutar el código de nuestro ejemplo es el siguiente.

Resultado de ejecutar el código de ejemplo

El código de ejemplo lo pueden descargar aquí.

En el próximo post vamos a discutir el Patrón llamado Proxy Remoto.

Recordá suscribirte aquí para recibir las últimas actualizaciones todas las semanas.

Referencias:
Libro Head First Design Patterns. Versión digital.

Leave a Reply

Your email address will not be published. Required fields are marked *