oop

oop - ¿Es la lógica de negocios en los constructores una buena idea?



(5)

Dividir las cosas. Realmente, no desea un ticket para saber cómo debe enviar el correo electrónico, etc. Ese es el trabajo de un servicio como la clase.

[Actualización] No creo que los patrones de fábrica sugeridos sean buenos para esto. Una fábrica es útil si desea crear diferentes implementaciones de tickets sin poner esta lógica en el ticket (por ejemplo, mediante constructores sobrecargados).

Eche un vistazo al concepto de servicio propuesto en el diseño impulsado por dominios.

Servicios : Cuando una operación no pertenece conceptualmente a ningún objeto. Siguiendo los contornos naturales del problema, puede implementar estas operaciones en los servicios. El concepto de servicio se llama "Fabricación pura" en GRASP.

Actualmente estoy reconstruyendo un sistema de tickets especializado en el trabajo (utilizado principalmente para ayudar a las personas con fallas en el hardware de detección remota ...). De todos modos, me preguntaba si realizar una gran cantidad de actividades de tipo de flujo de trabajo en el constructor de un objeto es una buena idea.

Por ejemplo, actualmente hay esto:

$ticket = new SupportTicket( $customer, $title, $start_ticket_now, $mail_customer );

tan pronto como se crea el objeto, colocará una fila en una base de datos, enviará al cliente un correo electrónico de confirmación, posiblemente enviará un mensaje de texto al técnico más cercano, etc.

¿Debería un constructor despedir todo ese trabajo, o algo más parecido a lo siguiente?

$ticket = new SupportTicket($customer, $title); $customer->confirmTicketMailed($ticket); $helpdesk->alertNewTicket($ticket);

Si ayuda, todos los objetos se basan en el estilo ActiveRecord.

Supongo que puede ser una cuestión de opinión, pero ¿qué crees que es lo mejor que puedes hacer?


El problema, desde una perspectiva de diseño OO, no es tanto si esa funcionalidad debe implementarse en un constructor (como lo es en otros métodos en esa clase), sino si la clase SupportTicket debe saber cómo hacer todas esas cosas.

En pocas palabras, la clase SupportTicket debería ser modelar un ticket de soporte, y solo un ticket de soporte. Crear un correo electrónico, saber cómo enviar ese correo electrónico al cliente, poner en cola el ticket para su procesamiento, etc. es una gran cantidad de funcionalidades que debe pasar de su clase SupportTicket y encapsular en otro lugar. Los beneficios de hacer esto incluyen un acoplamiento más bajo, una mayor cohesión y una mejor capacidad de prueba.

Eche un vistazo al Principio de Responsabilidad Única , que explica los beneficios de hacer esto. En particular, este blog es un buen lugar para leer sobre SRP y los otros principios clave de un buen diseño de OO.


En realidad, nunca he estado contento con ninguna de las respuestas disponibles, pero echémosle un vistazo. Las opciones se basan en dos preguntas de evaluación:

E1. ¿Dónde pertenece el conocimiento de la lógica empresarial?

E2. ¿Dónde se verá el próximo lector del código? ( Código Obvio Screechingly )

Algunas opciones:

  • En el código del cliente (el objeto que hace "SupportTicket nuevo"). Es probable que no sepa / no sepa la lógica de negocios, evidentemente, o de lo contrario no querría crear ese constructor de fantasía. Si ES el lugar adecuado para la lógica empresarial, debería decir:

    $ticket = new SupportTicket($customer, $title); handleNewSupportTicket($ticket, ...other parameters...)

    donde, para proteger E2, "handlenewSupportTicket" es el lugar donde se define esa lógica de negocios (y el programador siguiente puede encontrarla fácilmente).

  • En el objeto de ticket de soporte , como una llamada de negocios por separado. Personalmente, no estoy realmente contento con esto, porque son dos llamadas del código del cliente, donde el pensamiento mental es 1 cosa.

    $ticket = new SupportTicket($customer, $title); $ticket -> handleNewSupportTicket(...other parameters...)

  • En la clase de tickets de soporte . Aquí se espera que la lógica de negocios se encuentre en el área de Ticket de soporte, pero como los nuevos tickets de soporte deben manejarse inmediatamente y no más tarde, esta tarea importante no puede dejarse en manos de la imaginación o el error de escritura, es decir, no específicamente a codigo del cliente. Solo sé cómo codificar los métodos de clase en Smalltalk , pero voy a probar el pseudocódigo:

    $ticket = SupportTicket.createAndHandleNewSupportTicket(...other parameters...)

    Suponiendo que el código del cliente necesita el identificador del nuevo ticket para otros fines (de lo contrario, "$ ticket =" desaparecería).

    No me gusta mucho esto, porque a otros programadores no les resulta tan natural buscar la lógica de negocios en clase o en métodos estáticos. Pero es la tercera opción.

  • La única cuarta opción es si hay otro lugar donde la lógica de negocios reside felizmente y otros programadores la buscarán de forma natural, en cuyo caso entra en una función de clase / estática allí.

    $ticket = SupportTicketBusinessLogic.createAndHandleNewSupportTicket(...other params...)

    donde esa clase / función estática hace las múltiples llamadas necesarias. (Pero ahora, una vez más, tenemos la posibilidad de que los boletos se puedan construir y no manejar correctamente :(.


La respuesta corta es no.

En el diseño de hardware, solíamos tener un dicho: "No coloques una puerta en el reloj o en la línea de reinicio, esto oculta la lógica". Lo mismo se aplica aquí por la misma razón.

La respuesta más larga tendrá que esperar, pero consulte el " Código de Screetchingly ".


Si el constructor hace todo ese trabajo, entonces el constructor conoce muchos otros objetos de dominio. Esto crea un problema de dependencia. ¿Debería el ticket saber realmente sobre el Customer y el servicio de HelpDesk ? Cuando se agregan nuevas funciones, ¿no es probable que se agreguen nuevos objetos de dominio al flujo de trabajo, y eso no significa que nuestro pobre ticket tendrá que saber acerca de una población cada vez mayor de objetos de dominio?

El problema con las telarañas de dependencia como esta es que un cambio en el código fuente de cualquiera de los objetos del dominio tendrá un impacto en nuestro pobre ticket . El ticket tendrá tanto conocimiento del sistema que no importa lo que pase, el ticket estará involucrado. Encontrará desagradables las sentencias if reunidas dentro de ese constructor, verificando la base de datos de configuración, el estado de la sesión y muchas otras cosas. El ticket crecerá para convertirse en una clase de dios.

Otra razón por la que no me gustan los constructores que hacen cosas es que hace que los objetos que los rodean sean muy difíciles de probar. Me gusta escribir muchos objetos simulados. Cuando escribo una prueba contra el customer , quiero pasarle un ticket . Si el constructor del ticket controla el flujo de trabajo y la danza entre el customer y otros objetos de dominio, es poco probable que pueda burlarse de él para probar al customer .

Le sugiero que lea The SOLID Principles , un artículo que escribí hace varios años sobre la gestión de dependencias en diseños orientados a objetos.