Tuesday, March 10, 2009

OO Basics: Create the right object

Most people learn structured programming before they are exposed to object oriented programming. That's just the nature of the beast, since you need to learn the basics of a language before you can move on. At least that's the latest theory. The problem with structured programming is you learn to use flags to signal changes in state. You then end up with ifs and switches all over the code. For example


if (item.isTaxable()) {
item.setCost(item.getCost *= TAX_RATE);
}


The root of the problem is that you only have one kind of Item.


public class Item {
private boolean taxable;

public boolean isTaxable() {
return taxable;
}
...
}

where two types are needed:

public interface Item {
double getTax();
}

public class TaxableItem implements Item {
public double getTax() {
return cost * TAX_RATE;
}
}

public class NontaxableItem implements Item {
public double getTax() {
return 0;
}
}

You also have a problem with how the object is created:

Item item = new Item(true);



Say you are implementing a shopping cart, and as the user adds items to the cart, the correct type of item is added. Then at the end, you can calculate tax for the taxable items because the cart already contains the right kind of item.

cart.addItem(ItemFactory.createItem(itemId));

Now there are no ifs in the domain code:

for (Item item : cart.items()) {
tax += item.getTax();
}

The moral: a lot of ifs and their accompanying headaches go away when you take the effort to create the right object.

2 comments:

Karl the Pagan said...

I really like the technique of returning a zero/empty valued constant. Not only can it simplify the higher-level logic surrounding your data structures as you demonstrate, but it is a good way to slim down the memory use of your objects by coding to the most common use case.

For example Sun's LogRecord has fields describing calling class and method, parameters, and the specific level (when the normal use case is "info"). So I would reduce the record to only message, threadId, and millis time. Other fields can be made use of by subclassing.

Anonymous said...

Good post, Curtis. Good rule of thumb for me to remember!

DR