Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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?

share|improve this question
3  
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 Jan 3 '11 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. –  Winston Ewert Jan 3 '11 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 Jan 4 '11 at 0:43
add comment

4 Answers

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.

share|improve this answer
2  
When in doubt add another class. –  Chris Jan 3 '11 at 19:58
add comment

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.

share|improve this answer
    
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. –  Winston Ewert Jan 3 '11 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 Jan 3 '11 at 20:02
add comment

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(...)

share|improve this answer
    
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. –  Winston Ewert Jan 3 '11 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) –  Winston Ewert Jan 3 '11 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. –  Winston Ewert Jan 3 '11 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 Jan 4 '11 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 Jan 4 '11 at 2:40
show 8 more comments

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.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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