Many things in real life have common properties. Both dogs and cats are animals, for example. Objects can
have common properties as well, which you can clarify using a generalization between their classes. By
extracting common properties into classes of their own, you will be able to change and maintain the system
more easily in the future.
A generalization shows that one class inherits from another. The inheriting class is called a descendant.
The class inherited from is called the ancestor. Inheritance means that the definition of the ancestor -
including any properties such as attributes, relationships, or operations on its objects - is also valid
for objects of the descendant. The generalization is drawn from the descendant class to its ancestor class.
Generalization can take place in several stages, which lets you model complex, multilevel inheritance
hierarchies. General properties are placed in the upper part of the inheritance hierarchy, and special
properties lower down. In other words, you can use generalization to model specializations of a more
general concept.
Example
In the Recycling Machine System all the classes - Can, Bottle, and Crate - describe different types of
deposit items. They have two common properties, besides being of the same type: each has a height and a
weight. You can model these properties through attributes and operations in a separate class, Deposit Item.
Can, Bottle, and Crate will inherit the properties of this class.
The classes Can, Bottle, and Crate have common properties height and weight. Each is a specialization of
the general concept Deposit Item.
A class can inherit from several other classes through multiple inheritance, although generally it will
inherit from only one.
There are a couple of potential problems you must be aware of if you use multiple inheritance:
-
If the class inherits from several classes, you must check how the relationships, operations, and
attributes are named in the ancestors. If the same name appears in several ancestors, you must describe
what this means to the specific inheriting class, for example, by qualifying the name to indicate its
source of declaration.
-
If repeated inheritance is used; in this case, the same ancestor is being inherited by a descendant
more than once. When this occurs, the inheritance hierarchy will have a "diamond shape" as shown below.
Multiple and repeated inheritance. The Scrolling Window With Dialog Box class is inheriting the Window
class more than once.
A question that might arise in this context is "How many copies of the attributes of Window are included in
instances of Scrolling Window With Dialog Box?" So, if you are using repeated inheritance, you must have a
clear definition of its semantics; in most cases this is defined by the programming language supporting the
multiple inheritance.
In general, the programming language rules governing multiple inheritance are complex, and often difficult
to use correctly. Therefore using multiple inheritance only when needed, and always with caution is
recommended.
A class that is not instantiated and exists only for other classes to inherit it, is an abstract class.
Classes that are actually instantiated are concrete classes. Note that an abstract class must have at least
one descendant to be useful.
Example
A Pallet Place in the Depot-Handling System is an abstract entity class that represents properties common
to different types of pallet places. The class is inherited by the concrete classes Station, Transporter,
and Storage Unit, all of which can act as pallet places in the depot. All these objects have one common
property: they can hold one or more Pallets.
The inherited class, here Pallet Place, is abstract and not instantiated on its own.
Because class stereotypes have different purposes, inheritance from one class stereotype to another does
not make sense. Letting a boundary class inherit an entity class, for example, would make the boundary
class into some kind of hybrid. Therefore, you should use generalizations only between classes of the same
stereotype.
You can use generalization to express two relationships between classes:
-
Subtyping, specifying that the descendant is a subtype of the ancestor. Subtyping means that the
descendant inherits the structure and behavior of the ancestor, and that the descendant is a type of
the ancestor (that is, the descendant is a subtype that can fill in for all its ancestors in any
situation).
-
Subclassing, specifying that the descendant is a subclass (but not a subtype) of the ancestor.
Subclassing means that the descendant inherits the structure and behavior of the ancestor, and that the
descendant is not a type of the ancestor.
You can create relationships such as these by breaking out properties common to several classes and placing
them in a separate classes that the others inherit; or by creating new classes that specialize more general
ones and letting them inherit from the general classes.
If the two variants coincide, you should have no difficulty setting up the right inheritance between
classes. In some cases, however, they do not coincide, and you must take care to keep the use of
inheritance understandable. At the very least you should know the purpose of each inheritance relationship
in the model.
Subtyping means that the descendant is a subtype that can fill in for all its ancestors in any situation.
Subtyping is a special case of polymorphism, and is an important property because it lets you design all
the clients (objects that use the ancestor) without taking the ancestor's potential descendants into
consideration. This makes the client objects more general and reusable. When the client uses the actual
object, it will work in a specific way, and it will always find that the object does its task. Subtyping
ensures that the system will tolerate changes in the set of subtypes.
Example
In a Depot-Handling System, the Transporter Interface class defines basic functionality for communication
with all types of transport equipment, such as cranes and trucks. The class defines the operation
executeTransport, among other things.
Both the Truck Interface and Crane Interface classes inherit from the Transporter Interface; that is,
objects of both classes will respond to the message executeTransport. The objects may stand in for
Transporter Interface at any time and will offer all its behavior. Thus, other objects (client objects) can
send a message to a Transporter Interface object, without knowing if a Truck Interface or Crane Interface
object will respond to the message.
The Transporter Interface class can even be abstract, never instantiated on its own. In which case, the
Transporter Interface might define only the signature of the executeTransport operation, whereas the
descendant classes implement it.
Some object-oriented languages, such as C++, use the class hierarchy as a type hierarchy, forcing the
designer to use inheritance to subtype in the design model. Others, such as Smalltalk-80, have no type
checking at compilation time. If the objects cannot respond to a received message they will generate an error
message.
It may be a good idea to use generalization to indicate subtype relationships even in languages without
type checking. In some cases, you should use generalization to make the object model and source code easier
to understand and maintain, regardless of whether the language allows it. Whether or not this use of
inheritance is good style depends heavily on the conventions of the programming language.
Subclassing constitutes the reuse aspect of generalization. When subclassing, you consider what parts of an
implementation you can reuse by inheriting properties defined by other classes. Subclassing saves labor and
lets you reuse code when implementing a particular class.
Example
In the Smalltalk-80 class library, the class Dictionary inherits properties from Set.
The reason for this generalization is that Dictionary can then reuse some general methods and storage
strategies from the implementation of Set. Even though a Dictionary can be seen as a Set (containing
key-value pairs), Dictionary is not a subtype of Set because you cannot add just any kind of object to a
Dictionary (only key-value pairs). Objects that use Dictionary are not aware that it actually is a Set.
Subclassing often leads to illogical inheritance hierarchies that are difficult to understand and to
maintain. Therefore, it is not recommended that you use inheritance only for reuse, unless something else
is recommended in using your programming language. Maintenance of this kind of reuse is usually quite
tricky. Any change in the class Set can imply large changes of all classes inheriting the class Set. Be
aware of this and inherit only stable classes. Inheritance will actually freeze the implementation of the
class Set, because changes to it are too expensive.
The use of generalization relationships in design should depend heavily on the semantics and proposed use
of inheritance in the programming language. Object-oriented languages support inheritance between classes,
but nonobject-oriented languages do not. You should handle language characteristics in the design model. If
you are using a language that does not support inheritance, or multiple inheritance, you must simulate
inheritance in the implementation. In which case, it is better to model the simulation in the design model
and not use generalizations to describe inheritance structures. Modeling inheritance structures with
generalizations, and then simulating inheritance in the implementation, can ruin the design.
If you are using a language that does not support inheritance, or multiple inheritance, you must simulate
inheritance in the implementation. In this case, it is best to model the simulation in the design model and
not use generalizations to describe inheritance structures. Modeling inheritance structures with
generalizations, and then only simulating inheritance in the implementation can ruin the design.
You will probably have to change the interfaces and other object properties during simulation. It is
recommended that you simulate inheritance in one of the following ways:
-
By letting the descendant forward messages to the ancestor.
-
By duplicating the code of the ancestor in each descendant. In this case, no ancestor class is created.
Example
In this example the descendants forward messages to the ancestor via links that are instances of
associations.
Behavior common to Can, Bottle, and Crate objects is assigned to a special class. Objects for which this
behavior is common send a message to a Deposit Item object to perform the behavior when necessary.
|