Información derramada a través de límites de objetos

10

Muchas veces mis objetos comerciales tienden a tener situaciones en las que la información necesita cruzar los límites de los objetos con demasiada frecuencia. Al hacer OO, queremos que la información esté en un objeto y, en la medida de lo posible, todo el código relacionado con esa información debe estar en ese objeto. Sin embargo, las reglas comerciales no siguen este principio, lo que me causa problemas.

Como ejemplo, suponga que tenemos un Pedido que tiene un número de Artículos de pedido que se refiere a un Artículo de inventario que tiene un precio. Invoco Order.GetTotal () que suma el resultado de OrderItem.GetPrice () que multiplica una cantidad por InventoryItem.GetPrice (). Hasta ahora tan bueno.

Pero luego descubrimos que algunos artículos se venden con un trato de dos por uno. Podemos manejar esto haciendo que OrderItem.GetPrice () haga algo como InventoryItem.GetPrice (cantidad) y dejando que InventoryItem se encargue de esto.

Sin embargo, luego descubrimos que el acuerdo de dos por uno solo dura un período de tiempo particular. Este período de tiempo debe basarse en la fecha del pedido. Ahora cambiamos OrderItem.GetPrice () para que sea InventoryItem.GetPrice (quatity, order.GetDate ())

Pero luego tenemos que admitir precios diferentes dependiendo de cuánto tiempo haya estado el cliente en el sistema: InventoryItem.GetPrice (cantidad, orden.GetDate (), orden.GetCustomer ())

Pero luego resulta que las ofertas de dos por uno se aplican no solo a la compra de múltiples del mismo artículo de inventario, sino a múltiples para cualquier artículo en una categoría de inventario. En este punto, levantamos nuestras manos y simplemente le damos al InventoryItem el artículo de pedido y le permitimos viajar sobre el gráfico de referencia del objeto a través de los accesores para obtener la información que necesita: InventoryItem.GetPrice (this)

TL; DR Quiero tener un bajo acoplamiento en los objetos, pero las reglas comerciales a menudo me obligan a acceder a información de todas partes para tomar decisiones particulares.

¿Existen buenas técnicas para lidiar con esto? ¿Otros encuentran el mismo problema?

Winston Ewert
fuente
44
A primera vista parece ser que la clase InventoryItem está intentando hacer demasiado calculando el precio, devolver un precio dado es una cosa, pero los precios de venta y las reglas comerciales especiales no deberían ser direcciones en InventoryItem. Despliegue una clase para calcular sus precios y deje que maneje la necesidad de datos del Pedido, Inventario y Cliente, etc.
Chris
@ Chris, sí, dividir la lógica de precios es probablemente una buena idea. Mi problema es que dicho objeto estará estrechamente acoplado a todos los objetos relacionados con el orden. Esa es la parte que me molesta y la parte que me pregunto si puedo evitar.
Winston Ewert
1
Si algo, no quiero llamarlo de ninguna manera, necesita toda esa información, entonces de alguna manera debe consumir esta información para producir el resultado deseado. Si ya tiene un inventario, un artículo, un cliente, etc., la implementación de la lógica de negocios en su propia área tiene más sentido. Realmente no tengo una mejor solución.
Chris

Respuestas:

6

Básicamente, hemos tenido la misma experiencia donde trabajo y la hemos abordado al tener una clase OrderBusinessLogic. En su mayor parte, el diseño que ha descrito funciona para la mayoría de nuestro negocio. Es agradable, limpio y simple. Pero en esas ocasiones en las que ha comprado 2 de esta categoría, tratamos eso como una "exención comercial" y hacemos que la clase OrderBL recalcule los totales atravesando los objetos necesarios.

Es una solución perfecta, no. Todavía tenemos una clase que sabe demasiado sobre las otras clases, pero al menos hemos trasladado esa necesidad fuera de los objetos de negocio a una clase de lógica de negocios.

Walter
fuente
2
En caso de duda, agregue otra clase.
Chris
1

Suena como que necesitan un objeto separado de descuento (o una lista de ellos) que realiza un seguimiento de todas esas cosas, y luego aplicar el descuento (s) de la Orden, algo así como Order.getTotal(Discount)o Discount.applyTo(Order)o similar.

John Bode
fuente
Puedo ver dónde sería mejor poner el código en un descuento en lugar de en el objeto de inventario. Pero el objeto de descuento parece que aún tendría que acceder a la información de todos los diferentes objetos para hacer su trabajo. Así que eso, al menos hasta donde puedo ver, no resuelve el problema.
Winston Ewert
2
Creo que el objeto de descuento es una buena idea, pero lo haría implementar un patrón de observador ( en.wikipedia.org/wiki/Observer_pattern ), con los descuentos como el sujeto (s) del patrón
BlackICE
1

Está perfectamente bien acceder a datos de otras clases. Sin embargo, desea que esta sea una relación unidireccional. Por ejemplo, supongamos que ClassOrder accede a CallItem. Idealmente, ClassItem no debería acceder a ClassOrder. Lo que creo que falta en su Clase de orden es algún tipo de lógica de negocios que puede o no garantizar una clase como la sugerida por Walter.

Editar: Respuesta al comentario de Winston

No creo que necesite un objeto de inventario en absoluto ... al menos en la forma en que lo está utilizando. En cambio, tendría una clase de inventario que administrara una base de datos de inventario.

Me referiría a los artículos de inventario por una identificación. Cada pedido contendría una lista de ID de inventario y una cantidad correspondiente.

Luego calcularía el total del pedido con algo como esto.
Inventory.GetCost (Items, customerName, date)

Entonces podría tener otra función auxiliar como:

Inventory.ItemsLefts (int itemID)
Inventory.AddItem (int itemID, int cantidad)
Inventory.RemoveItem (int itemID, int cantidad)
Inventory.AddBusinessRule (...)
Inventory.DeleteBusinessRule (...)

Pemdas
fuente
Está perfectamente bien pedir datos de clases estrechamente relacionadas. El problema surge cuando estoy accediendo a datos de clases indirectamente relacionadas. Implementar esa lógica en la clase de pedido requeriría que la clase de pedido se ocupe directamente del objeto Inventario (que contiene los datos de precios necesarios). Esa es la parte que me molesta porque está acoplando Order a clases de las cuales (idealmente) no sabría nada.
Winston Ewert
Esencialmente, su idea sería eliminar OrderItem en su lugar, Order realiza un seguimiento de toda esa información en sí. Entonces es fácil pasar esa información a otro objeto para obtener información sobre precios. Eso podría funcionar. (En el escenario particular, este ejemplo se basa libremente en el artículo de pedido era demasiado complicado y realmente necesitaba ser su propio objeto)
Winston Ewert
Lo que no tiene sentido para mí es por qué desea que se haga referencia al inventario utilizando números de identificación en lugar de referencias de objetos. Me parece mucho mejor hacer un seguimiento de las referencias y cantidades de objetos y luego de los identificadores y referencias de los artículos.
Winston Ewert
Realmente no estoy sugiriendo eso en absoluto. Creo que aún debe tener una clase o estructura para los artículos de pedido. Simplemente no creo que los objetos individuales de los artículos de inventario deban tener el precio en su interior principalmente porque no estoy seguro de cómo lo pondrían allí sin preguntar algún tipo de base de datos. El pedido o artículo de inventario sería estrictamente una clase de datos que contiene un identificador de artículo y una cantidad. El pedido en sí tendría una lista de estos objetos.
Pemdas
No estoy realmente seguro de lo que estás preguntando en el segundo comentario. No todo tiene que ser un objeto. Realmente no estoy seguro de cómo ubicar un artículo específico sin algún tipo de identificación.
Pemdas
0

Después de pensar más en esto, he ideado mi propia estrategia alternativa.

Definir una clase de precio. Inventory.GetPrice()devuelve un objeto de precio

Price OrderItem::GetPrice()
{
    return inventory.GetPrice().quantity(quantity);
}

Currency OrderItem::GetTotal()
{
    Price price = empty_price();
    for(item in order_items)
    {
         price = price.combine( item.GetPrice() )
    }
    price = price.date(date).customer(customer);
    return price.GetActualPrice();
}

Ahora la clase Price (y posiblemente algunas clases relacionadas) encapsulan la lógica de fijación de precios y el orden no tiene que preocuparse por eso. Price no sabe nada sobre Order / OrderItem, sino que simplemente recibe información.

Winston Ewert
fuente