Por ejemplo:
Solo se pueden actualizar las solicitudes de empleo que aún no están en revisión o aprobadas. En otras palabras, una persona puede actualizar su formulario de dispositivo de trabajo hasta que RR. HH. Comience a revisarlo, o ya sea aceptado.
Entonces, una solicitud de empleo puede estar en 4 estados:
APLICADO (estado inicial), EN REVISIÓN, APROBADO, RECHAZADO
¿Cómo logro tal comportamiento?
Seguramente puedo escribir un método update () en la clase de aplicación, verificar el estado de la aplicación y no hacer nada o lanzar una excepción si la aplicación no está en el estado requerido
Pero este tipo de código no hace obvio que tal regla existe, permite que cualquiera llame al método update (), y solo después de fallar un cliente sabe que tal operación no estaba permitida. Por lo tanto, el cliente debe ser consciente de que tal intento podría fallar, por lo tanto, tenga cuidado. El hecho de que el cliente sea consciente de tales cosas también significa que la lógica se está escapando.
Intenté crear diferentes clases para cada estado (ApprovedApplication, etc.) y puse las operaciones permitidas solo en las clases permitidas, pero este tipo de enfoque también se siente mal.
¿Existe un patrón de diseño oficial, o un simple fragmento de código, para implementar tal comportamiento?
this kind of code does not make it obvious such a rule exists
- Por eso el código tiene documentación. Los escritores de buen código seguirán los consejos de Euphoric y proporcionarán un método para que el exterior pruebe la regla antes de probar el hardware.Respuestas:
Este tipo de situación aparece con bastante frecuencia. Por ejemplo, los archivos solo se pueden manipular mientras están abiertos, y si intenta hacer algo con un archivo después de que se haya cerrado, obtendrá una excepción de tiempo de ejecución.
Su deseo ( expresado en su pregunta anterior ) de utilizar el sistema de tipos del lenguaje para asegurarse de que no ocurra lo incorrecto es noble, ya que los errores en tiempo de compilación siempre son preferibles a los errores de tiempo de ejecución. Sin embargo, no conozco ningún patrón de diseño para este tipo de situación, probablemente porque terminaría causando más problemas de los que resolvería. (Sería poco práctico).
Lo más cercano a su situación que conozco es modelar diferentes estados de un objeto que corresponden a diferentes capacidades a través de interfaces adicionales, pero de esta manera solo está reduciendo el número de lugares en el código donde puede ocurrir un error de tiempo de ejecución, usted es no erradicando la posibilidad de un error de tiempo de ejecución.
Entonces, en su situación, declararía una serie de interfaces que describen lo que se puede hacer con su objeto en sus diversos estados, y su objeto devolvería una referencia a la interfaz correcta en una transición de estado.
Entonces, por ejemplo, el
approve()
método de su clase devolvería unaApprovedApplication
interfaz. La interfaz se implementaría de forma privada (a través de una clase anidada), por lo que el código que solo tiene una referencia a unApplication
no puede invocar ninguno de losApprovedApplication
métodos. Luego, el código que manipula una aplicación aprobada declara explícitamente su intención de hacerlo en el momento de la compilación al requerirApprovedApplication
que trabaje con. Pero, por supuesto, si almacena esta interfaz en algún lugar y luego la utiliza después dedecline()
que se haya invocado el método, aún obtendrá un error de tiempo de ejecución. No creo que haya una solución perfecta para su problema.fuente
if( someone.hasApprovalPermission( application ) ) { application.approve(); }
el principio de Separación de Preocupaciones indica que ni a la aplicación, ni a nadie, debe preocuparse por tomar decisiones con respecto a los permisos y la seguridad.Asiento con la cabeza en diferentes partes de las diversas respuestas, pero parece que el OP todavía tiene la preocupación del control de flujo. Hay demasiado para tratar de fusionarse en palabras. Solo voy a corregir un código: el patrón de estado.
Nombres de estado como tiempo pasado
"In_Review" no es un estado quizás sino una transición o proceso. De lo contrario, los nombres de sus estados deben ser coherentes: "Solicitud", "Aprobación", "Rechazo", etc. O también "Revisado". O no.
El estado Aplicado realiza una transición de revisión y establece el estado en Revisado. El estado revisado realiza una transición de aprobación y establece el estado en Aprobado (o Rechazado).
Editar - Comentarios de manejo de errores
Un comentario reciente:
Y de la pregunta original:
Hay más trabajo de diseño que hacer. No existe
Unified Field Theory Pattern
. La confusión proviene de suponer que el marco de transición de estado realizará funciones generales de aplicación y manejo de errores. Eso se siente mal porque lo es. La respuesta que se muestra está diseñada para controlar el cambio de estado.Esto sugiere que hay tres funcionalidades trabajando aquí: el estado, la actualización y la interacción de las dos. En este caso
Application
no es el código que he escrito. Puede usarlo para determinar el estado actual.Application
No es elapplicationPaperwork
tampoco.Application
No es la interacción de los dos, pero podría ser unaStateContextEvaluator
clase general . AhoraApplication
orquestará estas interacciones de componentes y luego actuará en consecuencia, como emitir un mensaje de error.Fin de edición
fuente
Application
constructor donde se produce la excepción. Tal vez la llamadaAppliedState.Approve()
podría dar como resultado un mensaje de usuario "La solicitud debe revisarse antes de que pueda aprobarse".AppliedState.apply()
recordarle suavemente al usuario que la solicitud ya se ha enviado y está esperando su revisión. Y el programa continúa.En general, lo que está describiendo es un flujo de trabajo. Más específicamente, las funciones comerciales que están encarnadas por estados como REVISADO APROBADO o DECLINADO se encuadran bajo el título de "reglas de negocios" o "lógica de negocios".
Pero para ser claros, las reglas comerciales no deben codificarse en excepciones. Hacerlo sería usar excepciones para el control de flujo del programa, y hay muchas buenas razones por las que no debe hacer eso. Las excepciones deben usarse para condiciones excepcionales, y el estado NO VÁLIDO de una aplicación es completamente excepcional desde el punto de vista comercial.
Use excepciones en los casos en que el programa no pueda recuperarse de una condición de error sin la intervención del usuario ("archivo no encontrado", por ejemplo).
No existe un patrón específico para escribir lógica empresarial, aparte de las técnicas habituales para organizar sistemas de procesamiento de datos comerciales y escribir código para implementar sus procesos. Si las reglas de negocios y el flujo de trabajo son elaborados, considere usar algún tipo de servidor de flujo de trabajo o motor de reglas de negocios.
En cualquier caso, los estados REVISAR, APROBADO, RECHAZADO, etc. se pueden representar mediante una variable privada de tipo enum en su clase. Si usa métodos getter / setter, puede controlar si los setters permitirán cambios o no al examinar primero el valor de la variable enum. Si alguien intenta escribir en un regulador cuando el valor de enumeración está en un estado incorrecto, entonces se puede lanzar una excepción.
fuente
Application
podría ser una interfaz y podría tener una implementación para cada uno de los estados. La interfaz podría tener unmoveToNextState()
método, y esto ocultaría toda la lógica del flujo de trabajo.Para las necesidades del cliente, también podría haber un método que devuelva directamente lo que puede hacer y no (es decir, un conjunto de valores booleanos), en lugar de solo el estado, para que no necesite una "lista de verificación" en el cliente (supongo el cliente para ser un controlador MVC o UI de todos modos).
Sin embargo, en lugar de lanzar una excepción, simplemente no puede hacer nada y registrar el intento. Esto es seguro en tiempo de ejecución, se aplicaron reglas y el cliente tenía formas de ocultar los controles de "actualización".
fuente
Un enfoque para este problema que ha sido extremadamente exitoso en la naturaleza es hipermedia: la representación del estado de la entidad se acompaña de controles hipermedia que describen los tipos de transiciones que actualmente están permitidas. El consumidor consulta los controles para descubrir qué se puede hacer.
Es una máquina de estado, con una consulta en su interfaz que le permite descubrir qué eventos puede activar.
En otras palabras: estamos describiendo la web (REST).
Otro enfoque es tomar su idea de diferentes interfaces para diferentes estados y proporcionar una consulta que le permita detectar qué interfaces están disponibles actualmente. Piense en IUnknown :: QueryInterface, o en la conversión. El código del cliente juega Mother May I con el estado para averiguar qué está permitido.
Es esencialmente el mismo patrón: solo usar una interfaz para representar los controles hipermedia.
fuente
Aquí hay un ejemplo de cómo puede abordar esto desde una perspectiva funcional, y cómo ayuda a evitar las posibles dificultades. Estoy trabajando en Haskell, lo que supongo que no sabes, así que lo explicaré en detalle a medida que avance.
Esto define un tipo de datos que puede estar en uno de los cuatro estados que corresponden a los estados de su aplicación.
ApplicationDetails
se supone que es un tipo existente que contiene la información detallada.Un alias de tipo que necesita conversión explícita hacia y desde
Application
. Esto significa que si definimos la siguiente función que acepta y desenvuelveUpdatableApplication
y hace algo útil con ella,entonces tenemos que convertir explícitamente la Aplicación a una Aplicación Actualizable antes de que podamos usarla. Esto se hace usando esta función:
Aquí hacemos tres cosas interesantes:
UpdatableApplication
(que solo involucra una nota de compilación de tipo del cambio de tipo que se agrega, ya que Haskell tiene una característica específica para hacer este tipo de truco de nivel de tipo, no cuesta nada en tiempo de ejecución) yOption
en C # oOptional
en Java; es un objeto que envuelve un resultado que podría faltar).Ahora, para realmente armar esto, necesitamos llamar a esta función y, si el resultado es exitoso, pasarlo a la función de actualización ...
Como la
updateApplication
función necesita el objeto envuelto, no podemos olvidar comprobar las condiciones previas. Y debido a que la función de verificación de precondición devuelve el objeto envuelto dentro de unMaybe
objeto, tampoco podemos olvidar verificar el resultado y responder en consecuencia si falla.Ahora ... podrías hacer esto en un lenguaje orientado a objetos. Pero es menos conveniente:
Maybe
ellos, generalmente no tienen una forma tan conveniente de extraer los datos y elegir la ruta a seguir al mismo tiempo. La coincidencia de patrones también es realmente útil aquí.fuente
Puede usar el patrón «comando» y luego pedirle al Invoker que proporcione una lista de funciones válidas de acuerdo con el estado de la clase de receptor.
Usé lo mismo para proporcionar funcionalidad a las diferentes interfaces que se suponía que iban a llamar a mi código, algunas de las opciones no estaban disponibles dependiendo del estado actual del registro, por lo que mi invocador actualizó la lista y de esa manera cada GUI le preguntó al Invoker qué opciones estaban disponibles y se pintaron en consecuencia.
fuente