Derramamento de informações entre limites de objetos

10

Muitas vezes, meus objetos de negócios tendem a ter situações em que as informações precisam cruzar os limites de objetos com muita frequência. Ao fazer OO, queremos que as informações estejam em um objeto e, tanto quanto possível, todo código que lida com essas informações esteja nesse objeto. No entanto, as regras de negócios não seguem esse princípio, causando problemas.

Como exemplo, suponha que tenhamos um Pedido que tenha vários OrderItems que se refiram a um InventoryItem que tenha um preço. Invoco Order.GetTotal () que soma o resultado de OrderItem.GetPrice () que multiplica uma quantidade por InventoryItem.GetPrice (). Por enquanto, tudo bem.

Mas então descobrimos que alguns itens são vendidos com um acordo de dois por um. Podemos lidar com isso fazendo com que OrderItem.GetPrice () faça algo como InventoryItem.GetPrice (quantidade) e deixe InventoryItem lidar com isso.

No entanto, descobrimos que o acordo de dois por um dura apenas um período de tempo específico. Esse período precisa ser baseado na data do pedido. Agora, alteramos OrderItem.GetPrice () para InventoryItem.GetPrice (quatity, order.GetDate ())

Porém, precisamos oferecer suporte a preços diferentes, dependendo de quanto tempo o cliente está no sistema: InventoryItem.GetPrice (quantidade, pedido.GetDate (), pedido.GetCustomer ())

Porém, as transações de dois por um se aplicam não apenas à compra de vários itens do mesmo inventário, mas também a vários itens de uma categoria de inventário. Nesse ponto, levantamos as mãos e fornecemos o item de pedido ao InventoryItem e permitimos que ele percorra o gráfico de referência do objeto por meio de acessadores para obter as informações necessárias: InventoryItem.GetPrice (this)

TL; DR Quero ter um baixo acoplamento de objetos, mas as regras de negócios geralmente me forçam a acessar informações de todo o lugar para tomar decisões específicas.

Existem boas técnicas para lidar com isso? Outros acham o mesmo problema?

Winston Ewert
fonte
4
À primeira vista, parece que a classe InventoryItem está tentando fazer muito calculando o preço, retornar um determinado preço é uma coisa, mas os preços de venda e regras comerciais especiais não devem ser endereços no InventoryItem. Crie uma classe para calcular seus preços e deixe que ela lide com a necessidade de dados de Pedido, Estoque e Cliente e assim por diante.
Chris
@ Chris, sim, dividir a lógica de preços é provavelmente uma boa ideia. Meu problema é que esse objeto será fortemente acoplado a todos os objetos relacionados à ordem. Essa é a parte que me incomoda e a parte que me pergunto se posso evitar.
Winston Ewert
11
Se alguma coisa, eu não quero chamá-lo de nada, precisa de todas essas informações, de alguma forma ela deve consumir essas informações para produzir o resultado desejado. Se você já possui estoque, item, cliente e assim por diante, a implementação da lógica de negócios em sua própria área faz mais sentido. Eu realmente não tenho uma solução melhor.
Chris

Respostas:

6

Tivemos basicamente a mesma experiência em que trabalho e resolvemos isso tendo uma classe OrderBusinessLogic. Na maioria das vezes, o layout que você descreveu funciona para a maioria dos nossos negócios. É agradável, limpo e simples. Mas nas ocasiões em que você compra 2 desta categoria, tratamos isso como uma "execução comercial" e a classe OrderBL recalcula os totais percorrendo os objetos necessários.

É uma solução perfeita, não. Ainda temos uma classe sabendo muito sobre as outras classes, mas pelo menos transferimos essa necessidade dos objetos de negócios para uma classe de lógica de negócios.

Walter
fonte
2
Em caso de dúvida, adicione outra classe.
Chris
1

Parece que você precisa de um objeto separado de desconto (ou uma lista deles) que mantém o controle de todas as coisas, e depois aplicar (s) o desconto à Ordem, algo como Order.getTotal(Discount)ou Discount.applyTo(Order)ou similar.

John Bode
fonte
Eu posso ver onde seria melhor colocar o código em um desconto do que no objeto Inventário. Mas o objeto de desconto que parece ainda precisaria acessar informações de todos os diferentes objetos para realizar seu trabalho. Portanto, isso não resolve, pelo menos até o ponto, o problema.
Winston Ewert
2
Eu acho que o objeto Discount é uma boa ideia, mas eu o faria implementar um padrão de observador ( en.wikipedia.org/wiki/Observer_pattern ), com os descontos como assunto (s) do padrão
BlackICE
1

Não há problema em acessar dados de outras classes. No entanto, você deseja que este seja um relacionamento direcional. Por exemplo, suponha que ClassOrder acesse CallItem. Idealmente, ClassItem não deve acessar ClassOrder. O que acho que está faltando na sua classe de pedidos é algum tipo de lógica comercial que pode ou não justificar uma classe como a sugerida por Walter.

Edit: Resposta ao comentário de Winston

Eu não acho que você precise de um objeto de item de inventário ... pelo menos da maneira que você está usando. Em vez disso, eu teria uma classe de inventário que gerenciava um banco de dados de inventário.

Eu me referiria a itens de inventário por um ID. Cada pedido conteria uma lista de IDs de inventário e uma quantidade correspondente.

Então eu calcularia o total do pedido com algo assim.
Inventory.GetCost (Itens, customerName, date)

Então você pode ter outra função auxiliar como:

Inventory.ItemsLefts (int ID do item)
Inventory.AddItem (int ID do item, quantidade int)
Inventory.RemoveItem (int itemID, quantidade int)
Inventory.AddBusinessRule (...)
Inventory.DeleteBusinessRule (...)

Pemdas
fonte
É perfeitamente bom solicitar dados de classes estreitamente relacionadas. O problema surge quando estou acessando dados de classes indiretamente relacionadas. A implementação dessa lógica na classe Order exigiria que a classe Order lidasse diretamente com o objeto Inventory (que contém os dados de precificação necessários). Essa é a parte que me incomoda porque está acoplando Order a classes sobre as quais (idealmente) ela não saberia nada.
Winston Ewert
Essencialmente, sua idéia seria eliminar o OrderItem, em vez disso, o Order controla todas as informações em si. É fácil passar essas informações para outro objeto para obter informações sobre preços. Isso poderia funcionar. (No scenarion particular, este exemplo é vagamente baseado no OrderItem era muito complicado e realmente tinha necessidade de ser seu próprio objeto)
Winston Ewert
O que não faz sentido para mim é por que você deseja que o Inventário seja referenciado usando números de identificação e não referências a objetos. Parece-me muito melhor acompanhar as referências e quantidades de objetos do que os IDs e referências de itens.
Winston Ewert
Não estou realmente sugerindo isso. Acho que você ainda deve ter uma classe ou estrutura para os itens do pedido. Eu simplesmente não acho que os objetos individuais do item de inventário devam ter o preço interno, principalmente porque não tenho certeza de como você o incluiria sem pedir algum tipo de banco de dados. O item do pedido ou do estoque seria estritamente uma classe de dados contendo um identificador de item e uma quantidade. O pedido em si teria uma lista desses objetos.
pemdas
Não tenho muita certeza do que você está perguntando no segundo comentário. Nem tudo tem que ser um objeto. Eu realmente não tenho certeza de como você localizaria um item específico sem algum tipo de identificação.
pemdas
0

Depois de pensar mais sobre isso, criei minha própria estratégia alternativa.

Defina uma classe de preço. Inventory.GetPrice()retorna um objeto de preço

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();
}

Agora, a classe Price (e possivelmente algumas classes relacionadas) encapsula a lógica do preço e o pedido não precisa se preocupar com isso. O preço não sabe nada sobre Order / OrderItem, apenas fornece informações.

Winston Ewert
fonte