Why OO? WAS Re: [thelist] php design question

Paul Cowan evolt at funkwit.com
Tue Nov 19 23:39:01 CST 2002


(this is long, and will wrap oddly, and stuff like that. sorry)

OK. I'll have a go at explaining 'Why O-O?' in one simple step (well....)

I will fail, but by god, I will try.

This is just _one_ example of where it might be useful; it is almost
certainly not a brilliant one.

Take as read all the previous statements by others wiser than I: that O-O
in a 'web environment' _may_ be less useful (also, _may not_) than it is
elsewhere, and a lot of people aren't convinced about its usefulness in
'real coding' (ahem) either. But let's say, for the sake of running a
simulation through the Paul Cowan Arg-U-Matic 9000i (tm) Automatic
Point Generator, that you're running a hardware store, and you're
writing a stock management system.

You sell:
    - rope and wooden planks (sold by length)
    - hammers and tractor tyres (sold per-item)

In an 'O-O world', you might define a hierarchy of objects (often
abstract, rather than concrete), and their properties, like so:
    Item
    |
    +-- Length-Priced Item
    |   |
    |   +-- Rope
    |   +-- Planks
    |
    +-- Unit-Priced Item
        |
        +-- Hammers
        +-- Tractor tyres

The 'item' object might have the properties 'length', 'count',
and 'unit cost'. By implication, everything that 'hangs off' the
'item' object in the tree also has these properties, with no
extra code required.

You can then define methods (functions) on each object -- or, indeed,
on some of the objects, and not on others.

Let's take the method 'CalculatePrice' (which is surely going to
prove useful) and have some pseudocode in a new O-O language of my
own devising:

    method Item.CalculatePrice
    [
        result = (count * unitcost)
    ]

Because that's defined in the 'Item' class, any of the classes that
hang off that (Rope, Planks, ...) can use that too. But that's not right
for rope or planks... so maybe we define

    method LengthPricedItem.CalculatePrice
    [
        result = (length * unitcost)
    ]

then rope and planks work right -- the new method in the 'parent class'
just 'flows through' to those objects.  We don't need to redefine it
in the others, that would be silly.

How is this useful? Well, let's compare price calculations. In the
O-O code, we've got:

    function CalculateStateTax(item)
    [
        result = item.CalculatePrice * 10%
    ]

in the non-O-O code, we might replace the functions above with:

    function CalculatePrice(itemtype, length, quantity, unitcost)
    [
        if (itemtype is 'rope') or (itemtype is 'plank') then
            result = length * unitcost
        else
            result = quantity * unitcost
    ]

    function CalculateStateTax(itemtype, length, quantity, unitcost)
    [
        if (itemtype is 'rope') or (itemtype is 'plank') then
            result = length * unitcost * 10%
        else
            result = quantity * unitcost * 10%
    ]


So have we gained anything by using O-O? No, not really. All we've
done is move the code around, really. It's the same code. At least
with non-O-O, all the price code is in one place, right?

But What happens if we change 'plank' from being length-based to
unit-based? (stupid example, I know) Well, we have to change the 'if' in
two places in the non-O-O one -- CalculatePrice and CalculateStateTax.
(or three, or four, or five, or fifty...)

'Aha!' I hear you think. 'He's duplicating code to make the non-O-O look
bad'. So let's simplify CalculateStateTax to:
    [
        Tax = CalculatePrice(itemtype, length, quantity, unitcost) * 10%
    ]
and do the same elsewhere -- we functionalise the code more, and well we
should. If we're lucky, we can only have the price calculation in one
spot. Much better, isn't it?

But then what happens when we add weight-based products: nails and
lawn-seed?

If we change CalculatePrice to add ", weight" to the parameters, fine. But
then we have to change CalculateStateTax to have the same parameter:
and all the calls to it, and also the code and calls for
CalculateFederalTax, and CalculateBuyerRebate, and CalculateIncentivePoints,
and CanPurchaseFitOnCustomer'sLineOfCredit, and.... so on.

What do we do in an O-O model? We add
    ...
    |
    +-- Weight-Priced Item
        |
        +-- Nails
        +-- Lawn Seed

And then
    method WeightPricedItem.CalculatePrice
    [
        result = (weight * unitcost)
    ]

and we're done. That is it. Seriously, that might be all the code we
need (for that part, anyway).

When you're calculating tax, you're dealing with an 'abstract class' of
'Item': you don't know, or indeed care, HOW the item calculates its
own price. You just say 'Item, what's your price?' and it works it out.
The point here is _encapsulation_. The item knows about its own internal
workings; everything else which deals with items just knows 'hey, items
have prices'. It doesn't care how it does it; if it changes, your
'calling code' won't care a whit. That way, you're creating 'chunks'
of code which are rather more modular and, often (but not _always_)
more useful and more maintainable.

I'm sure, Rudy, you'd be familiar with the belief (not always for the
best, but often so) that you should manipulate items close to where they
'live'.

e.g.: Manipulate data in the data layer; manipulate business rules in the
business layer. Most people enforce referential integrity in their DB,
not in their CF code; most people implement business processes in their
CF code, not their DB. Usually, it makes sense to do this.

O-O's "encapsulation" concept is kind of like this, but taken a step
further.

(of course, my examples here are not very good; but there is a limit
to the complexity I'm willing to convey in an email which has already
taken me ages to write. I hope they're better than the rather abstract
menu example though.)

There are just two parts of O-O which can prove useful: encapsulation
and inheritance -- and on rereading, I see that I haven't explained
inheritance, one of the most powerful parts, very well at all. Ahh well.
There's a reason whole _books_ are written on this, you know.

And remember: Object-Orientation is a methodology, not a panacea. Anybody
who says "you should rewrite that in O-O", without being able to explain
why, is a shyster. "Because you'll be able to re-use your code better
and maintenance is easier" isn't a good reason. If someone says that,
give them your DB diagram, your business requirements doc, and say
"give me a concrete example". If there isn't one, O-O is pointless for
you. Of course, just because that particular person can't come up with
one doesn't mean there isn't one, but it might help you work out who's
saying it just for the sake of saying it.

Anyone who says "you should always use O-O" is a chump; no more so,
though, than someone who says "O-O is always useless bloat". You
will find that, more often than not, both of the people making these
bold factual assertions (and people DO) is not only a moron, but
_does not know what they are talking about_ on one side or the other.

None is so blind as he who will not see etc. etc.

Paul.




More information about the thelist mailing list