Generalizations are a great way to inherit common attributes and operations, but a class inherits much more from a superclass, namely associations, constraints (limits), methods (code for an operation), interfaces (specification of an operation), and composite parts (the parts internal to a class).
Think about an instance of a generic dwelling unit (a superclass) and its associated instance of an address. Now think of a particular kind of dwelling unit, say a ranch style house (a subclass). The ranch unit is also associated with an address because it is a kind of dwelling unit. The ranch unit subclass inherits the dwelling unit’s association with the address class. If you constrain the definition of any dwelling unit’s size attribute to be no smaller than six square feet, then that ranch unit’s size could not be any smaller than six square feet. The ranch unit inherits any constraints of the superclass dwelling unit. You could reuse the dwelling unit’s method for calculating its own resale value based on size and location for calculating the ranch houses resale value based on the same formula. You also inherit the composite parts of a dwelling unit such as the kitchen, living room and bedroom in any type of dwelling like the ranch-style house.
Warning You should be careful when inheriting from a superclass. The regulations for using inheritance are a little complex, but we’ll show you the rules that you use most often. When your superclass is associated with other classes, then the subclasses (being special cases of the superclass) are also associated with those same classes. Even so, you should be aware that only certain aspects of an association are inherited. (See the next section, “Making sense of inherited associations,” for more information.) When your subclass specifies constraints, they must be the same as—or more constraining than—those of the superclass. Operations and their methods may be simply reused, or redefined.
In the archive example that we used earlier in this chapter, it turns out that storage space—like shelves and file cabinets—store all manner of archive media. Videotapes are stored on special movable shelves. Transcripts are stored in file cabinets of various sizes. To demonstrate this connection, we modeled this situation as an association between the class StorageSpace and the class ArchiveMedium.
All the subclasses of ArchiveMedium inherit this association and so RecordedMedia is associated also with StorageSpace. However, the subclasses of ArchiveMedium only inherit certain features of the association.
You inherit the role name, multiplicity and constraints on the far side of an inherited association. For example, RecordedMedia inherits the multiplicity at the StorageSpace end of the association between ArchiveMedium and StorageSpace. You inherit any constraints and qualifiers on the near side of an inherited association. The near side of the association would be the ArchiveMedium side of the association between ArchiveMedium and StorageSpace.
Figure 6-7 illustrates the far-side features that the subclasses RecordedMedia, PhotoMedia, and PrintMedia inherit from ArchiveMedium in the stores association: 0..1 multiplicity and mediaLocation role name. The subclasses are also forced to be ordered because the stores association has the near-side constraint ({ordered}).
Figure 6-7: Inherited features of an association.
You can override inheritance—change aspects of inherited attributes, constraints, and the methods used for operations. For example, an attribute of a subclass can redefine an attribute inherited from the superclass. Additionally the method used to implement an operation in a subclass can be a refined version of the operation inherited from the superclass. For example, all types of vehicles (the superclass) can move (the superclass operation). However, each type of vehicle like a sailboat and a car (subclasses) move in very different ways (different subclass methods for the inherited move operation).
When overriding attributes inherited by a subclass keep the following in mind with examples illustrated in Figure 6-8:
Figure 6-8: Examples of overridden attributes.
Redefined name: An attribute in a subclass can redefine an attribute in the superclass by showing a constraint with the word redefines followed by the name of the redefined attribute from the superclass.
The class ArchiveMedium has an attribute defined as inventoryID: Text, and the subclass Book inherits that attribute but redefines it as isbn: String {redefines inventoryID}.
Datatypes: The datatype of an inherited attribute must be the same as or a specialization of the inherited attribute’s datatype in the superclass.
In the superclass ArchiveMedium the inventoryID has the datatype Text. The subclass RecordedMedia defines the datatype for inventoryID as String. String being a specialization of Text.
Default value: The default value of an attribute in a subclass may override the default value of that same attribute in the superclass.
The generation attribute in the class ArchiveMedium has a default value of one because it is assumed that most of the material in the archive is an original and not a copy. However, all the photos in the archive are copies from a private collection. So, the generation attribute of PhotoMedia has a default value of 2.
Derived attribute: The subclass may have a derived attribute that was not a derived attribute in the superclass.
ArchiveMedium has a weight attribute that is not a derived attribute. However, the Transcript subclass inherits the weight attribute from ArchiveMedium, and inherits the sheetWeight attribute of PrintMedia. The weight attribute of Transcript is a derived attribute because it can be calculated using the sheetWeight and the numPages attributes. (Transcript inherits both sheetWeight and numPages from PrintMedia.)
Inevitably, you deal with business rules that constrain the objects in your system. For instance, the archivist must follow the rule that no material (ArchiveMedium) may be borrowed from the archive for longer than thirty days. You recognize this as one of those rules people have to follow, and you have to make sure your software doesn’t violate that rule. The archive-system software must warn the archivist when any instance of ArchiveMedium is out for a period close to (but not more than) thirty days.
This case illustrates an important principle: If the superclass has a constraint or limitation, then all of its subclasses have that constraint too. When you use inheritance, your subclasses must not loosen any constraints placed on the superclass. Therefore Books and Transcripts cannot be borrowed for more than thirty days.
Although you can’t loosen the constraint for subclasses, you can tighten it. One example is the rule that Videotapes can’t be borrowed for more than a week.
One thing we like about inheritance is being able to reuse the method for an operation defined in a superclass. Often the method code for a superclass operation has to be written no more than once; all the subclasses then have that operation. No need to write the method code again (once for each subclass). The original operation of ArchiveMedium has a simple method that works the same for every subclass. The method (using the Java language) looks something like this:
public Boolean original() { if (generation == 1) then return True; //first generation means original else return False; }
Although you can reuse your inherited operations and their methods, you can do more than simply reuse the method code. You can extend, restrict, or optimize your methods. For a concrete idea of these different ways to override methods in the superclass, consider ArchiveMedium and its place operation:
private StorageSpace mediaLocation; //attribute to implement // the association to an instance of StorageSpace public void place (StorageSpace on){ if (on.spaceAvailabe()) //check to see if there is space if (on.add(this)) //add media to storage space mediaLocation= on; //set pointer to our media location }
Now let’s look at what it means to extend, restrict, or optimize a method:
Extend: Reuse the method code you inherit from the superclass and then add some code that extends the method to deal with specialized attributes of the subclass. For example the Transcript classes’ place operation must make sure the editor of the transcript has access to the place where the transcript is stored. So the place operation is extended to check that condition:
public void place (StorageSpace on) { if (on.userAccess(editor)) //extension is here super.place(on); //then reuse superclass method }
Restrict: Your method code in the subclass must account for some additional constraint that is placed on the subclass. In the archive example, a videotape must not be placed in a crowded storage area. So the place method of the Videotape class must be restricted to storage spaces that are no more than 80 percent full:
public void place (StorageSpace on) { if (on.percentUsed() <= 80) //check for enough space super.place(on); //then reuse superclass method }
Optimize: You optimize the method code for a subclass because you can take into account the specialized extra attributes or constraints in the subclass. It turns out that photographs are so thin that we almost never have to worry about whether there’s enough storage space available. So, to optimize the code for the place method a little, you can remove the statement that first checks to see whether space is available. The resulting code looks like this:
public void place (StorageSpace on) { if (on.add(this)) //add media to storage space mediaLocation= on; //set pointer to our media location }
Classes have public operations that you invoke from instances of other classes. You can think of each one of these public operations as being an interface between you and the internal workings of the class. Each operation is defined by its name, parameters, and return-result type. This definition is known as the operation’s signature. For instance the signature for the assign operation on the ArchiveMedium class includes the name assign, the to argument and its datatype String, as well as the Boolean return result type. In UML the signature for assign looks like this:
assign(to:String): Boolean
Your subclasses inherit this signature as well as the method code for that operation. When you invoke the assign operation on any subclass of ArchiveMedium, your subclasses must all have the assign operation with one parameter—and the operation will return a Boolean value, no matter how you write the method code for the subclasses.
Normally you create instances of classes. Each class has methods defined for each operation. A method must follow the rules laid down by the operation’s signature. The classes used to create instances are known as concrete classes. Most examples of classes in this book are concrete classes. However, suppose you have a superclass operation with no method code for that operation. Such an operation—without method code—is known as an abstract operation. In UML, abstract operations are shown in italics. If an operation is abstract (has no method), then you can’t create instances of that class. The runtime environment wouldn’t know what to do if you invoked an operation that had no method code. In this situation, any of your classes with abstract operations are known as abstract classes. Any class for which you cannot create instances is an abstract class. In UML, abstract classes have their class names shown in italics.
Abstract classes are a great way to enforce interface inheritance. If you specify an abstract operation in a superclass, then all of its subclasses must conform to the signature of that operation. So anyone who inserts a new subclass into the inheritance hierarchy must write method code for the inherited abstract operation to create a concrete subclass.
Remember You cannot create instances of abstract classes. You can only create instances of concrete classes.
In Figure 6-9, you see that recommendPlaybackMachine is an abstract operation and RecordedMedia is an abstract class. We don’t have enough information in the superclass to define a method that could recommend what equipment to use to play back recorded media. On the other hand, we have that information in each of the subclasses. Given (for example) an instance of the VideoTape class and a value for its format attribute, we have all the data we need to make a recommendation.
Figure 6-9: An abstract class, used to enforce interface inheritance.