2009-03-07 11 views
15

Actualmente estoy reconstruyendo un sistema de ticket especializado en el trabajo (principalmente utilizado para ayudar a personas con fallas en el hardware de detección remota ...). De todos modos, me preguntaba si hacer una gran cantidad de actividad de tipo de flujo de trabajo en el constructor de un objeto es una buena idea.¿Es buena idea la lógica comercial en los constructores?

Por ejemplo, en la actualidad esto:

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

tan pronto como se crea el objeto, que va a poner una fila en una base de datos, vaya y enviarlo por correo 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 estar ejecutando todo ese trabajo, o algo más como el siguiente?

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

Si ayuda, los objetos están basados ​​en el estilo ActiveRecord.

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

+0

Usted puede encontrar esto relevante:. [Debe ser constructores de código libre] (http: //www.yegor256. com/2015/05/07/ctors-must-be-code-free.html) – yegor256

Respuesta

38

Si el constructor hace todo eso, el constructor sabe sobre muchos otros objetos de dominio. Esto crea un problema de dependencia. ¿Debe el ticket conocer realmente el Customer y el HelpDesk? Cuando se agregan nuevas características, ¿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 creciente población de objetos de dominio?

El problema con spiderwebs de dependencia de esta manera es que un cambio de código fuente a cualquiera de los objetos de dominio tendrá un impacto en nuestro pobre ticket. El ticket tendrá tanto conocimiento del sistema que no importa lo que ocurra, el ticket estará involucrado. Encontrará declaraciones desagradables if reuniéndose dentro de ese constructor, verificando la base de datos de configuración, y 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 a su alrededor sean muy difíciles de probar. Me gusta escribir muchos objetos simulados. Cuando escribo una prueba en contra de customer quiero pasarlo por burlado ticket. Si el constructor de ticket controla el flujo de trabajo y el baile entre customer y otros objetos de dominio, entonces es poco probable que pueda simularlo para probar el customer.

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

+0

Dios habló directamente a usted, se siente bendecido. Robert C. Martin respondió su respuesta: O –

4

Divida las cosas. Realmente, no quieres un boleto para saber cómo debe enviar el correo electrónico, etc. Ese es el trabajo de un servicio como 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 en sí (a través de constructores sobrecargados, por ejemplo).

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

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.

4

El problema, desde una perspectiva de diseño orientado a objetos, no es tanto si esa funcionalidad debería ser implementado en un constructor (a diferencia de en otros métodos de esa clase), pero si la clase SupportTicket debe saber cómo hacerlo todas esas cosas

En pocas palabras, la clase SupportTicket debe 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. son todos trozos de funcionalidad que debe mover 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 a Single Responsibility Principle, que explica los beneficios de hacer esto. En particular, el blog this es un buen lugar para leer sobre SRP y los otros principios clave del buen diseño OO.

+0

Esto golpea el clavo en la cabeza. Si usas una fábrica (como otras publicaciones mencionan) o no es irrelevante. – Egwor

2

La respuesta corta es no.

En el diseño de hardware, solíamos tener un dicho, "No coloque una puerta en el reloj o en la línea de restablecimiento, ya que oscurece la lógica". Lo mismo se aplica aquí por la misma razón.

La respuesta más larga tendrá que esperar, pero consulte "ScreetchinglyObvious Code".

+0

Según el contexto aquí, el usuario "Alistair" es más probable [Alistair Cockburn] (https://en.wikipedia.org/wiki/Alistair_Cockburn). –

1

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

E1. ¿A dónde pertenece el conocimiento de la lógica comercial?

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

Algunas opciones:

  • En el código cliente (el objeto que hace "nueva SupportTicket"). Probablemente no/no debería saber la lógica comercial, evidentemente, o de lo contrario no querría crear ese constructor sofisticado. Si es el lugar correcto para la lógica de negocio, entonces debería decir:

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

    donde, con el fin de proteger a E2, "handlenewSupportTicket" es el lugar donde se define que la lógica de negocio (y la siguiente programador puede fácilmente Encuéntralo).

  • En el boleto de soporte objeto, como una llamada de negocios por separado. Personalmente, no estoy muy 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 negocio resida en el área de Boleto de soporte, pero dado que los nuevos tickets de soporte deben ser manejados inmediatamente y no más tarde, esta importante tarea no puede dejarse a la imaginación o el error de escritura de nadie, es decir, específicamente a la codigo del cliente. Sólo sé cómo codificar los métodos de clase en Smalltalk, pero voy a tomar una puñalada en pseudo-código:

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

    Suponiendo que el código de cliente necesita el identificador de la nueva entrada para otros fines (de lo contrario el "$ ticket = "desaparecería".

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

  • La cuarta opción es si hay otro lugar en el que la lógica de negocios resida felizmente y otros programadores lo buscarán naturalmente, 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 necesita las múltiples llamadas necesarias. (Pero ahora tenemos de nuevo la posibilidad de que los boletos pueden ser construidos y no se manejan adecuadamente :(

Cuestiones relacionadas