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.
It is fair to say, however, that most people's definitions of Object Orientation include three key principles:
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:
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.
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:
For example, the following kinds of entities might be good candidates for representation by a class:
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:
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.
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.
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.
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:
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.