10

Many times my business objects tend to have situations where information needs to cross object boundaries too often. When doing OO, we want information to be in one object and as much as possible all code dealing with that information should be in that object. However, business rules do not follow this principle giving me trouble.

As an example suppose that we have an Order which has a number of OrderItems which refers to an InventoryItem which has a price. I invoke Order.GetTotal() which sums the result of OrderItem.GetPrice() which multiples a quantity by InventoryItem.GetPrice(). So far so good.

But then we find out that some items are sold with a two for one deal. We can handle this by having OrderItem.GetPrice() do something like InventoryItem.GetPrice( quantity ) and letting InventoryItem deal with this.

However, then we find out that the two-for-one deal only lasts for a particular time period. This time period needs to be based on the date of the order. Now we change OrderItem.GetPrice() to be InventoryItem.GetPrice( quatity, order.GetDate() )

But then we need to support different prices depending on how long the customer has been in the system: InventoryItem.GetPrice( quantity, order.GetDate(), order.GetCustomer() )

But then it turns out that the two-for-one deals apply not just to buying multiple of the same inventory item but multiple for any item in a InventoryCategory. At this point we throw up our hands and just give the InventoryItem the order item and allow it to travel over the object reference graph via accessors to get the information its needs: InventoryItem.GetPrice( this )

TL;DR I want to have low coupling in objects, but business rules often force me to access information from all over the place in order to make particular decisions.

Are there good techniques for dealing with this? Do others find the same problem?

3
  • 4
    At first glance it seems to be that InventoryItem class is attempting to do too much by calculating the price, returning a given price is one thing but sale prices and special business rules should not be addresses in InventoryItem. Roll out a class for calculating your prices and let it handle the need for data from Order, Inventory and Customer and so forth.
    – Chris
    Commented Jan 3, 2011 at 20:00
  • @Chris, yes, splitting out the pricing logic is probably a good idea. My problem is that such an object will be tightly coupled to all of the order related objects. Thats the part that bothers me and the part I'm wondering if I can avoid. Commented Jan 3, 2011 at 21:07
  • 1
    If something, I do not want to call it anything, needs all of that information then somehow it must consume this information to produce the desired result. If you have inventory, item, customer and so forth already built, then implementing the business logic in its own area makes the most sense. I do not really have a better solution.
    – Chris
    Commented Jan 4, 2011 at 0:43

4 Answers 4

6

We've had basically the same experience where I work and we've addressed it by having a OrderBusinessLogic class. For the most part the layout you've described works for the majority of our business. It's nice and clean and simple. But on those occaisions where you have buy any 2 from this category, we treat that as an "business execption" and have the OrderBL class recalculate the totals by traversing the objects needed.

Is it a perfect solution, no. We still have one class knowing way too much about the other classes, but at least we've moved that need out of the business objects and into a business logic class.

1
  • 2
    When in doubt add another class.
    – Chris
    Commented Jan 3, 2011 at 19:58
1

Sounds like you need a separate Discount object (or a list of them) that keeps track of all that stuff, and then apply the Discount(s) to the Order, something like Order.getTotal(Discount) or Discount.applyTo(Order) or similar.

2
  • I can see where putting the code in a Discount rather then in the Inventory object would be better. But the discount object it seems would still have to access information from all of the different objects to get its job done. So that doesn't, at least as far as I can see, solve the problem. Commented Jan 3, 2011 at 19:22
  • 2
    I think the Discount object is a good idea, but I would make it implement an observer pattern (en.wikipedia.org/wiki/Observer_pattern), with the Discounts as the subject(s) of the pattern
    – BlackICE
    Commented Jan 3, 2011 at 20:02
1

It is perfectly okay to access data from other classes. However, you want this to be a one directional relationship. For example, suppose ClassOrder accesses CallItem. Ideally, ClassItem should not access ClassOrder. What I think you are missing in your Order Class is some sort of business logic that may or may not warrant a class such as Walter suggested.

Edit: Response to Winston's comment

I don't think you need an inventory item object at all...at least in the way that you are using it. I would instead have an inventory class that managed an inventory database.

I would refer to inventory items by an ID. Each order would contain a list of inventory ID's and a corresponding quantity.

Then I would calculate the total of the order with something like this.
Inventory.GetCost(Items,customerName,date)

Then you could have other helper function like:

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

13
  • Its perfectly fine to ask for data from closely related classes. The problem arises when I'm accessing data from indirectly related classes. To implement that logic in the Order class would require the Order class to be deal directly with the Inventory object (which contains the necessary pricing data). That's the part that bothers me because it is coupling Order to classes which (ideally) it would know nothing about. Commented Jan 3, 2011 at 19:33
  • Essentially, your idea would be to eliminate OrderItem instead Order keeps track of all that information itself. It is then easy to pass that information off to another object for pricing information. That could work. (In the particular scenarion this example is loosely based on the OrderItem was too complicated and really did need to be its own object) Commented Jan 3, 2011 at 23:04
  • What doesn't make sense to me is why you'd want to have Inventory be referenced using id numbers rather then object references. It seems way better to me to keep track of object references and quantities then item ids and references. Commented Jan 3, 2011 at 23:05
  • I am not really suggesting that at all. I think that you should still have a class or structure for order items. I just don't think that the individual inventory item objects should have the price inside mainly because I am not sure how you would get it in there with out asking some sort of database. The order or inventory item would strictly be a data class containing a item identifier and a quantity. The order itself would have a list of these objects.
    – Pemdas
    Commented Jan 4, 2011 at 2:36
  • I am not really sure what you are asking in the second comment. Not everything has to be an object. I am really not sure how you would locate a specific item without some sort of id.
    – Pemdas
    Commented Jan 4, 2011 at 2:40
0

After thinking about this more, I've come up with my own alternative strategy.

Define a Price class. Inventory.GetPrice() returns a price object

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

Now the Price class (and possibly some related classes) encapsulate the logic of pricing and order does not have to worry about it. Price doesn't know anything about Order/OrderItem instead simply having information fed into it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.