When to Use Object Orientation

Every year or two a new technology is hailed as the new paradigm for computer systems. New techniques solve formerly intractable problems with elegance and grace. The gurus of this brave new world attract disciples; the disciples become zealots; the zealots promise to sweep all before them.

After the initial swell of hype and enthusiasm, the Wave of the Future crashes upon the Rocks of Reality. The new techniques prove difficult to apply in the real world. Several large, visible, and expensive projects fail. Disillusionment sets in.

Eventually, we assimilate the new techniques into a larger body of practice and lore. They become old techniques, part of our usual bag of tricks, to be used where they are useful and ignored where they are not.

If you've been around for very long in this business, you've seen this cycle several times. Name your fad: structured programming, decision tables, relational databases, CASE tools, client-server, and all the rest.

We may be entering the later stages of this cycle for Object Orientation (OO). As with previous fads, OO has been less useful, and more difficult to apply, than we had hoped. When OO fails, the zealots insist that it wasn't properly applied; skeptics suspect that it was a bad idea in the first place.

The truth, as usual, is somewhere in between. Designing and building complex systems is still hard and probably always will be. While OO can be useful, it will be most useful if we can recognize the kinds of situations where it is least useful.

Let's make the working assumption that OO is good for some things and not so good for other things. The question is: which is which?

The following pages attempt some answers. They are not the proclamations of an expert, only the guesses of an amateur. They are an attempt to spark debate among those who are experts.

Let the debate begin.

THE OO PARADIGM

I won't even try to give an authoritative definition of Object Orientation. If you don't already know what it is, or think you do, then you probably wouldn't be reading this.

It is fair to say, however, that most people's definitions of Object Orientation include three key principles:

  1. Encapsulation
  2. Inheritance
  3. Polymorphism
Encapsulation is nothing new. A number of older terms refer to roughly the same idea: information hiding, modularity, cohesion.

In OO, encapsulation extends to procedures as well as data. In other words, a class encapsulates methods as well as attributes. Even this idea is not specific to OO. Abstract Data Types apply the same notion.

Inheritance is what makes a Class different from an Abstract Data Type. A derived class inherits the methods and attributes of its parent class.

Polymorphism allows different types of objects to behave differently. A piece of code can invoke an object's method without knowing what kind of object it is. The runtime system automagically selects the method appropriate for the class to which the specific object belongs. Without polymorphism, inheritance would be of limited usefulness.

Certain other ideas are often associated with OO, but are not really part of it:

Since I've never coded for a GUI myself, I have nothing to say on the subject. Implicit coding is a language issue rather than an OO issue, and a topic for a different diatribe.

AUSPICIOUS OMENS

Each of the following conditions makes it more likely that OO will be a good fit for a particular application:

None of these conditions is an absolute requirement. You may be able to use OO successfully even if none of them applies. However, these principles may help you decide when OO is likely to be a good fit and when it isn't.

FUNDAMENTAL AXIOM OF OO

One aspect of OO stands out in the literature, in my own experience, and, so far as I know, in the experience of pretty much anyone who has seriously used OO:
You have to get the class design right from the beginning. If you don't, there will be hell to pay later.
One reason is the emphasis on encapsulation. When you let one piece of code hide information from another, you'd better make sure that the other piece will never need that information. Hence the Fundamental Axiom applies with similar force to any discipline which emphasizes encapsulation.

The other reason is the emphasis on inheritance and polymorphism. Your entire system may depend on the design of your classes. Any change in the class design will ripple throughout the system.

Even without OO, it's a good idea to get the design right before coding. However, OO raises the stakes. An OO language like C++ provides elaborate machinery for defining class relationships, and for specifying the scope and degree of encapsulation. A seemingly minor design change can have wide-ranging and non-obvious consequences. A larger change may require extensive rewriting.

As a result, OO imposes a sizable penalty for guessing wrong about the design. Since it is so painful to change your design after you've started coding, you'll be tempted to muddle through with a flawed design rather than fix it.

FUNDAMENTAL COROLLARY OF OO

If you believe the Fundamental Axiom, then the Fundamental Corollary follows as night the day:
OO is most likely to work when you can get the design right from the beginning.
You're most likely to get the design right when:

STABLE REQUIREMENTS

An OO system typically uses classes to model the behavior of real-world entities. You're most likely to model these entities successfully if you understand them, and if they stay pretty much the same over the course of development.

For example, the following kinds of entities might be good candidates for representation by a class:

CONTROL OVER REQUIREMENTS

Sometimes our classes represent, not real-world entities, but useful abstractions whose characteristics are largely under our control. Often they represent entities which are internal to the program. For example:

INTUITIVE CLASSES

Ideally, objects should correspond to recognizable entities, whether real or abstract. The relationships among classes should reflect the relationships among the corresponding entities. The better the match, the easier it is to identify the appropriate attributes and methods, and the easier it is for others to understand the design.

Sometimes it is a struggle to come up with a satisfying set of classes. Things don't quite seem to fit. There may be several plausible designs, with no obvious reason to pick one instead of the others. This difficulty may reflect our own inexperience with OO design, or it may suggest that the OO paradigm doesn't work very well for this problem. Either way the result is much the same: an obscure and tortured design which will be awkward to implement and confusing to maintain.

Other times, we may find ourselves inventing classes as code gimmicks. They don't correspond very well to recognizable entities, but they help us make the code behave the way we want. Some of the advanced C++ idioms fall into this category. Such classes are not necessarily a bad idea, but they are often obscure.

These considerations suggest some guidelines:

PERSISTENT OBJECTS

OO techniques work best when every object resides in memory. They are more complicated when you must store objects in a file system.

One reason is that objects have different sizes and different internal structures, even if they are derived from the same base class. As long as they reside in memory, you can simply allocate space for them dynamically as needed. However, it is awkward to store different objects in the same file unless you are content with simple sequential access.

Another reason is that objects often incorporate data structures stitched together with pointers: linked lists, trees, and the like. You can't usefully store pointers in a file. At best you can only simulate them with file offsets, database keys, or various other gimmicks.

Pointers are likely to be involved in another form as well. Typically a polymorphic object carries a pointer to a list of its methods -- represented by entry point addresses, similar to function pointers in C and C++, or to procedure pointers in COBOL. It is by following these pointers that the runtime system supports polymorphism. When you store objects in a file, there's no simple way to store these pointers with it.

There are ways to solve these problems, but there is no simple standard way. You either code your own clever tricks or buy somebody else's proprietary clever tricks, such as an OO database.

MULTIPLE INHERITANCE

At first, multiple inheritance is an appealing idea, because things in the real world belong to multiple categories. I belong not only to the class Human, but also to various overlapping subclasses such as Employee, Husband, Voter, CarOwner, and CobolProgrammer. Each of these classes has its own distinct set of attributes and methods.

In practice, multiple inheritance exacts a price. Membership in multiple classes adds complexity by multiplying the number of attributes and methods which apply to an object. In addition, it introduces conflicts among competing base classes if they have attributes or methods with the same name. Languages which support multiple inheritance have ways of resolving these conflicts, but those ways tend to be either subtle or inflexible. At best they provide opportunities for errors and confusion.

There is no end to the categories to which something could belong. Your job as class designer is to find the minimal set of abstractions which are adequate for the job at hand.

Multiple inheritance may be appropriate in a given case. However, the temptation to use multiple inheritance may be a clue that the class design is muddled, or that OO is not a good fit.

POLYMORPHISM

If you see a natural and satisfying use for polymorphism, then OO is likely to be a good choice, for two reasons: For example: I once used polymorphism in a menu system. My base class MenuItem had a polymorphic method doAction and three subclasses: Program (which invoked some other program), Submenu, and Exit. The menu code didn't know or care which was which; it just invoked the doAction method for whichever item the user picked.

Later I added a fourth subclass PrevMenu, which returned to the previous menu without exiting. This addition was trivial because the menu code didn't have to change. If I hadn't used polymorphism, I would have had to find all the right IF statements and add another branch.

POOR CANDIDATES FOR OO

OO may be a poor choice for requirements which are complex, capricious, and unstable due to: These factors are bad news for any methodology, but OO can make it especially difficult to adapt to changing requirements. The people who change the rules don't care about your design. New rules may cross the boundaries which the initial design so carefully erected.

For example, consider a Human Resources system for managing payroll and benefits. (I've never worked on an HR system, so I'm just making things up. If I have committed any howlers they merely prove my ignorance; they don't disprove my point.)

Our employees are not all the same. Union members are governed by a collective bargaining agreement, and we withhold union dues from their paychecks. The others receive a different benefit package and pay no union dues.

In the bad old days before OO, you would have designed an Employee record with a flag to indicate union or non-union. Whenever necessary, your programs would test that flag with an IF statement.

Armed with OO methodology, however, you start off with a base class, Employee, for the things which are common to all employees: name, Social Security Number, home address, and so forth. Then you design two subclasses of Employee: UnionEmployee and Manager, with a CalculatePay method for each. Your program simply calls Employee.CalculatePay, and through the magic of polymorphism, selects the appropriate version of this method.

You're using a relational database, not a fancy OO database. Somehow you figure out a way your database can support two different kinds of employees, and you write a layer of interface code to translate between relational concepts and OO concepts.

However, it turns out that Managers aren't all alike, either. Some are paid by the hour, and others are salaried. No problem: you design two more subclasses, HourlyManager and SalariedManager, and tinker with your database accordingly.

In talking with the HR manager, you discover that upper-level managers, known as Officers, are entitled to additional benefits such as stock options. You add an Officer class.

A few of the Officers sit on the Board of Directors, along with some outside Directors. The company pays Directors for attending board meetings, and also pays insurance premiums to indemnify them against shareholder lawsuits. Two more classes enter the design, one of them using multiple inheritance to combine Officer and Director.

After coding for a while, you discover that union stewards (but not ordinary union members) can attend union committee meetings on company time. You add a UnionSteward class so that CalculatePay can reflect this rule.

After the system goes into production, your design must accommodate a series of further developments:

  1. The company creates a new category of part-time workers. They are not union members, and not entitled to most of the usual benefits.
  2. The company opens a plant in a right-to-work state. Some of the workers are covered by the collective bargaining agreement, but don't belong to the union or pay union dues.
  3. The company hires college students as management interns over the summer. They are salaried but receive few benefits.
  4. As a result of an SEC ruling, Officers who are also Directors are subject to additional reporting requirements when they receive or exercise stock options.
  5. The company hires temporary employees for seasonal work. They aren't covered by the collective bargaining agreement, and receive less pay and fewer benefits than union members.
  6. If a union member changes to part-time status, he or she can remain in the union for up to a year, with partial dues and partial benefits.
  7. The union negotiates a new contract, and wins a seat on the Board of Directors for one of the unionized employees. The union representative does not receive stock options, and attends on company time without additional compensation, except for reimbursement for travel and expenses. If the board meeting is held on a day which is a legal holiday in the state in which the representative is normally employed, he or she is paid at overtime rates.
By this point you may wish you had stuck with flags and IF statements.

I apologize for making this example so long, but I don't know how else to suggest the kinds of arbitrary complications that the real world can impose. If anything, the real world is even worse -- sometimes much worse.

An OO zealot might insist that a correct class design would have been able to accommodate these complications with a minimum of fuss. Perhaps so. My point is that in some problem domains it is unlikely that anyone but a virtuoso will come up with the correct design at the outset.

By definition, most people are mediocre. It's not smart to rely on a methodology which requires a virtuoso.


[home]Cobol Home [OOC]OO Cobol