Aplicar DRY a una jerarquía de herencia

7

Estoy trabajando en la refactorización de una aplicación heredada donde implementé el patrón de estado correctamente como se muestra en el siguiente diagrama:

Comove,hayuncomportamientocomúnentrelos3estados,asíquedecidíextraerelmétodocomúnRefund()aunaclaseabstractaprimariaRefundableStatedondeloimplementé:

Refactorizando más, me doy cuenta de que el método Ship() es común entre los 2 estados Cancelled y NewOrder , por lo que voy a otra ronda de extracción del comportamiento común a otra clase padre ShippableState :

Ahora necesito mi estado Cancelled para extender 2 clases y al mismo tiempo no puedo agrupar las funciones Refund() y Ship() ya que no todos los estados permiten las 2 acciones.

¿Cómo puedo solucionar esto?

Notas :

  • Esta pregunta es principalmente sobre la aplicación de DRY y la refactorización no sobre cómo implementar el patrón de estado.
  • Estoy usando PHP 5.3, pero estoy abierto a respuestas en cualquier otro idioma.
pregunta Songo 12.02.2013 - 10:31

4 respuestas

2

¿Por qué no mantener las cosas simples? Puede mantener su código DRY fácilmente sin ninguna clase adicional (su primer diseño) y, a diferencia de las otras respuestas aquí, en mi humilde opinión no es necesario sacrificar el principio DRY para este caso.

Si el método de "Reembolso" es igual en los tres casos, o el método de "Envío" en solo dos, implemente esos métodos en "Estado del pedido". Si la implementación es solo similar, pero no idéntica, refactorice las diferencias con los métodos de ayuda virtual que se anulan en las subclases (esto se denomina método de plantilla patrón ). Y si desea separar la interfaz de la implementación (que no tiene nada que ver con "DRY", solo con "separación de preocupaciones"), entonces reemplace la clase OrderState por una interfaz IOrderState , y deje que OrderState hereda de IOrderState , que proporciona implementaciones predeterminadas y código reutilizable para las otras subclases.

Si la clase "Enviado" no necesita una implementación "Enviar", ponga la funcionalidad común en un nuevo método OrderState.DoShip , y llame a ese método desde Cancelled.Ship y NewOrder.Ship , pero no desde Shipped.Ship (el último debe lanzar una excepción o no hacer nada, lo que sea apropiado en su caso).

Y si la implementación de los métodos de "Reembolso" o "Envío" alcanza un cierto tamaño, y necesita varios métodos de ayuda y quizás algunas variables de estado, entonces es hora de pensar en extraer esos métodos para ayudar a clases como "Refundir" o "Remitente" (que no forma parte de su jerarquía de herencia).

    
respondido por el Doc Brown 12.02.2013 - 14:04
3

En Java (no sé PHP), puede crear ShippableState, RefundableState (y CancellableState) como interfaces, crear la clase ShippableRefundableState que implementa ShippableState y RefundableState. Pero ¿qué pasa con CancellableState? Sin una herencia de implementación múltiple, no se puede instalar en ninguna parte sin repetirla.

Así que tal vez no uses herencia, usa composición. Así que tenga StateShipper / Refunder / Canceller con el método XXXState (estado) y delegue en sus estados.

En el lenguaje con herencia de implementación múltiple (por ejemplo, C ++), puede crear clases en lugar de interfaces y heredarlas en los estados. Mientras que las implementaciones múltiples no multipliquen la implementación de lo mismo, es bueno, las cosas terribles comienzan cuando no está claro (para el ser humano) de qué antepasado está algo.

    
respondido por el user470365 12.02.2013 - 10:55
2

Solo puedo decir que "DRY" no es una bala de plata. Debe utilizar el sentido común al decidir si se necesita una refactorización específica.

En su ejemplo, puede ver claramente que hay problemas al aplicar el enfoque 100% DRY. Creo que esta refactorización particular en este escenario no es necesaria. Especialmente el segundo paso.

Pero si realmente quieres hacer esto, entonces ...

Le recomendaría que abandone Diseño basado en herencia y comience a utilizar Diseño basado en interfaz . Este límite de herencia se creó porque los desarrolladores creaban soluciones "locas" e inmanejables. Si encuentra un problema de herencia múltiple, claramente está haciendo algo mal.

Con las interfaces, puede utilizar la inyección de composición y dependencia para almacenar código similar en un solo lugar.

    
respondido por el Michal Franc 12.02.2013 - 10:59
2

Aunque DRY es un buen principio, también puede excederse.

Incluso si cada uno de los estados admite un método de reembolso, eso no significa que se esté repitiendo. Eso solo sucedería si las reglas comerciales establecen que el manejo del reembolso debe ser idéntico en dos o más estados.
Pero incluso si hay un comportamiento común entre un grupo de estados, no veo una ventaja al agregar varias clases intermedias. En última instancia, todas las clases de estado en el patrón tienen que heredarse del mismo padre, por lo que el comportamiento común para varias operaciones también podría agruparse en esa clase base (especialmente si ese comportamiento común es indicar "operación no admitida").

    
respondido por el Bart van Ingen Schenau 12.02.2013 - 11:44

Lea otras preguntas en las etiquetas