• No results found

Classes can have more than one more general parent superclass. If this is part of your design, you are using

multiple inheritance. The world is full of such things, in reality, because the human mind imposes classification on the world, not vice versa. Humans are, if nothing else, inconsistent and complex, and we can find endless ways to classify things. Our thinking tends to overlap—good for thought, lousy for software system design.

A common reason for using multiple inheritance is the presence of more than one dimension to the data. If you model dimensions using inheritance trees, you wind up with multiple subtrees from a common superclass. As you break things down, you begin to see objects that combine the dimensions in meaningful ways, so you use multiple inheritance to bring the trees together.

One aspect of the commonplace book is the tracking of stolen property. "The goose, Mr. Holmes! The goose, sir!" he gasped.

"Eh! What of it, then? Has it returned to life, and flapped off through the kitchen window?" Holmes twisted himself round upon the sofa to get a fairer view of the man's excited face.

"See here, sir! See what my wife found in its crop!" He held out his hand, and displayed upon the centre of the palm a brilliantly scintillating blue stone, rather smaller than a bean in size, but of such purity and radiance that it twinkled like an electric point in the dark hollow of his hand.

Sherlock Holmes sat up with a whistle. "By Jove, Peterson," said he, "this is treasure trove indeed! I suppose you know what you have got?"

"A diamond, sir? A precious stone. It cuts into glass as though it were putty." "It's more than a precious stone. It's the precious stone."

"Not the Countess of Morcar's blue carbuncle!" I ejaculated.

"Precisely so. I ought to know its size and shape, seeing that I have read the advertisement about it in The Times every day lately. It is absolutely unique, and its value can only be conjectured, but the reward offered of £1000 is certainly not within a twentieth part of the market price."

… When the commissionaire had gone, Holmes took up the stone and held it against the light. "It's a bonny thing," said he. "Just see how it glints and sparkles. Of course it is a nucleus and focus of crime. Every good stone is. They are the devil's pet baits. In the larger and older jewels every facet may stand for a bloody deed. This stone is not yet twenty years old. It was found in the banks of the Amoy River in southern China, and is remarkable in having every characteristic of the carbuncle, save that it is blue in shade, instead of ruby red. In spite of its youth, it has already a sinister history. There have been two murders, a vitriol-throwing, a suicide, and several robberies brought about for the sake of this forty-grain weight of crystallized charcoal. Who would think that so pretty a toy would be a purveyor to the gallows and the prison? I'll lock it up in my strong box now and drop a line to the Countess to say that we have it." [BLUE]

In tracking these objects, there are two dimensions to consider: the kind of property involved and the insurance status of the property. Figure 7-7 shows an inheritance structure starting with the property. One branch of the hierarchy divides property into types by their nature: collectibles, jewelry, cash, security, personal, or "other" (to make the classification complete). The other branch of the hierarchy divides property into uninsured, privately insured, or publicly insured depending on the source of the theft insurance. These are all abstract classes.

The tricky part of this hierarchy comes when you need to create an actual object: you must combine the two subtrees into joint subclasses, such as Uninsured Jewelry or Publicly Insured Securities. In this case, you might practically have to define a subclass for the Cartesian product (all possible combinations of the abstract classes).

Note

I am not advocating this style of design. If anything, you should avoid this kind of design like the plague. There are ways around it, depending on your needs. The primary justification for this sort of design is to override operations in, and add structure to, the various dimensions. If you don't have much need to extend or restrict operations and structure, you shouldn't even consider the dimensional hierarchy approach; just use enumerated data types for the dimensions. In OR and OO products, you have many different options for representing dimensions. You should try to limit your inheritance to just what you need rather than going overboard with semantic distinctions that are pragmatically the same.

This form of multiple inheritance exhibits the infamous diamond shape, where the superclasses eventually merge in a single parent. This structure results in some hard problems in programming language design. If you call an operation, and that operation exists in both immediate superclasses and in the shared parent, which actual method executes? C++ forces you to specify the exact operation through a scope operator identifying the class owner, which increases your coupling. Worse, because of its structural logic, C++ gives you two copies of the shared parent, StolenProperty. You have to declare the intermediate classes virtual base classes in C++ to tell the compiler to share the parent. This has the result of forcing you to modify several classes if you want to add a class using multiple inheritance, again increasing coupling.

Think about the structure translated into persistent structures. You have a StolenProperty table, a table for each of the property types, and a table for each of the insurance types. It seems natural to add an InsuredStolenJewelry table if you need to represent attributes or operations that such an account has that its two parents do not. The object ID for stolen property flows through these tables representing the generalization relationships. A row in InsuredStolenJewelry has a one-to-one relationship with rows in InsuredStolenProperty and StolenJewelry. This represents the fact that an InsuredStolenJewelry object is also an InsuredStolenProperty and a StolenJewelry object.

If you then reverse-engineer this back into a UML diagram, your natural choice would be to use associations, not generalization, making the joint table a relationship rather than an entity. That is, instead of inheriting operations and structure, you refer to InsuredStolenJewelry as an association between StolenJewelry and InsuredStolenProperty, not as a subclass of both of them. This is one workaround for this kind of multiple inheritance—factoring the dimensions into separate classes and using associations to relate them [Blaha and Premerlani 1998, p. 60]. Dimensional multiple inheritance is not the only kind there is. A different reason for using it is to combine two

unrelated classes (that is, classes with no common parent). In most cases, this reflects the mix-in approach: you are mixing in behavior or structure that the subclass needs. For example, you may want at some point to treat

fingerprints as identification, but the Fingerprint class is a kind of image. Figure 7-8 shows what this looks like as multiple inheritance.

What this design does is to integrate the operations and structure from the Identification class into the Fingerprint class by subclassing both using multiple inheritance. The resulting FingerprintIdentification class acts as both a fingerprint record (with image) and as a kind of nonexpiring identification.

Note

Again, there are better ways to do this. Mix-in inheritance is better done through the use of interfaces. You create the interface you need for any kind of identification, then include it in the Fingerprint class or a subclass. See the next section, on "Subtypes: Type versus Class versus Interface," for a look at this example.

Multiple inheritance lets you say that a thing is a kind of several other things. To the extent that this reflects the true meaning of the thing, that's fine. Unfortunately, what you're doing when you subclass two or more classes is exponentially increasing the coupling of the system through inheritance. Inheritance coupling isn't all bad. For any given class, your shared attributes, operations, and associations inherited from a superclass make perfect sense from the perspective of the object. But as you increase the number of superclasses, you increase the

interdependence of more and more parts of the system.

When you then make a change to a part of the system, that change affects all its subclasses in some way. At the very least, you need to retest them. In a data model with persistent classes, that change is by definition persistent: you're changing the database schema. Migrating schemas under these circumstances is, if anything, more difficult than maintaining spaghetti code with meatball objects floating in it. The more interconnections you have, the more difficult maintenance and extension becomes. So, it's in your best interest to use multiple inheritance—and single inheritance, for that matter—with great restraint.

Figure 7-8: Fingerprints as Identification Using Mix-in Multiple Inheritance

The double-edged sword of multiple inheritance exists for one reason: to model objects that you want to use as a subtype of two or more different types. This is not as simple as it might appear.