Unit 4
Unit 4
The use of inheritance for the sole purpose of reusing code is called
implementation inheritance. With implementation inheritance, developers
reuse code quickly by subclassing an existing class and refining its
behavior. A Set implemented by inheriting from a Hashtable is an example
of implementation inheritance. Conversely, the classification of concepts
into type hierarchies is called specification inheritance (also called
“interface inheritance”). The UML class model of Figure 8-4 summarizes
the four different types of inheritance we discussed in this section.
Figure 8-4 Inheritance meta-model (UML class diagram). In object-oriented
analysis and design, inheritance is used for achieving several goals, in
particular modeling taxonomies and reusing behavior from abstract
classes. When modeling taxonomies, the inheritance relationships can be
identified either during specializations (when specialized classes are
identified after general ones) or during generalizations (when general
classes are abstracted out of a number of specialized ones). When using
inheritance for reuse, specification inheritance represents subtyping
relationships, and implementation inheritance represents reuse among
conceptually unrelated classes.
Delegation
Delegation is the alternative to implementation inheritance that should be
used when reuse is desired. A class is said to delegate to another class if it
implements an operation by resending a message to another class.
Delegation makes explicit the dependencies between the reused class and
the new class. The right column of Figure 8-3 shows an implementation of
MySet using delegation instead of implementation inheritance. The only
significant change is the private field table and its initialization in the
MySet() constructor. This addresses both problems we mentioned before:
• Extensibility. The MySet on the right column does not include the
containsKey() method in its interface and the new field table is private.
Hence, we can change the internal representation of MySet to another
class (e.g., a List) without impacting any clients of MySet.
• Subtyping. MySet does not inherit from Hashtable and, hence, cannot be
substituted for a Hashtable in any of the client code. Consequently, any
code previously using Hashtables still behaves the same way
Delegation is a preferable mechanism to implementation inheritance as it
does not interfere with existing components and leads to more robust code.
Note that specification inheritance is preferable to delegation in subtyping
situations as it leads to a more extensible design.
The Liskov Substitution Principle
The Liskov Substitution Principle [Liskov, 1988] provides a formal definition
for specification inheritance. It essentially states that, if a client code uses
the methods provided by a superclass, then developers should be able to
add new subclasses without having to change the client code. For
example, in the left column of Figure 8-3, this means that, if a client uses a
Hashtable, the client should not have to be modified when we substitute the
Hashtable for any of its subclasses, for example MySet. Clearly, this is not
the case, so the relationship between MySet and Hashtable is not a
specification inheritance relationship. Below is the formal definition of the
Liskov Substitution Principle.
Liskov Substitution Principle
If an object of type S can be substituted in all the places where an object of
type T is expected, then S is a subtype of T.
Interpretation
In object design, the Liskov Substitution Principle means that if all classes
are subtypes of their superclasses, all inheritance relationships are
specification inheritance relationships. In other words, a method written in
terms of a superclass T must be able to use instances of any subclass of T
without knowing whether the instances are of a subclass. Consequently,
new subclasses of T can be added without modifying the methods of T,
hence leading to an extensible system. An inheritance relationship that
complies with the Liskov Substitution Principle is called strict inheritance.
• specifying invariants
In this chapter, we provide an overview of the concepts of interface specification. We introduce OCL
(Object Constraint Language) as a language for specifying invariants, preconditions, and postconditions.
We discuss heuristics and stylistic guidelines for writing readable constraints. Finally, we examine the
issues related to documenting and managing interface specifications.
At this point in system development, we have made many decisions about the system and produced a
wealth of models:
• The analysis object model describes the entity, boundary, and control objects that are visible to the
user. The analysis object model includes attributes and operations for each object.
• Subsystem decomposition describes how these objects are partitioned into cohesive pieces that are
realized by different teams of developers. Each subsystem includes highlevel service descriptions that
indicate which functionality it provides to the others.
• Hardware/software mapping identifies the components that make up the virtual machine on which we
build solution objects. This may include classes and APIs defined by existing components.
• Boundary use cases describe, from the user’s point of view, administrative and exceptional cases that
the system handles.
• Design patterns selected during object design reuse describe partial object design models addressing
specific design issues.
All these models, however, reflect only a partial view of the system. Many puzzle pieces are still missing
and many others are yet to be refined. The goal of object design is to produce an object design model
that integrates all of the above information into a coherent and precise whole. The goal of interface
specification, the focus of this chapter, is to describe the interface of each object precisely enough so
that objects realized by individual developers fit together with minimal integration issues. To this end,
interface specification includes the following activities:
Identify missing attributes and operations. During this activity, we examine each subsystem
service and each analysis object. We identify missing operations and attributes that are needed
to realize the subsystem service. We refine the current object design model and augment it with
these operations.
Specify visibility and signatures. During this activity, we decide which operations are available to
other objects and subsystems, and which are used only within a subsystem. We also specify the
return type of each operation as well as the number and type of its parameters. This goal of this
activity is to reduce coupling among subsystems and provide a small and simple interface that
can be understood easily by a single developer.
Specify contracts. During this activity, we describe in terms of constraints the behavior of the
operations provided by each object. In particular, for each operation, we describe the conditions
that must be met before the operation is invoked and a specification of the result after the
operation returns.
The large number of objects and developers, the high rate of change, and the concurrent
number of decisions made during object design make object design much more complex than
analysis or system design. This represents a management challenge, as many important
decisions tend to be resolved independently and are not communicated to the rest of the
project. Object design requires much information to be made available among the developers so
that decisions can be made consistent with decisions made by other developers and consistent
with design goals. The Object Design Document, a live document describing the specification of
each class, supports this information exchange.
For example, consider the ARENA Game abstract class (Figure 9-2). The developer responsible for
realizing the Game class, including operations that apply to all Games, is a class implementor. The
League and Tournament classes invoke operations provided by the Game interface to organize and start
Matches. Developers responsible for League and Tournament are class users of Game. The TicTacToe
and Chess classes are concrete Games that provide specialized extensions to the Game class. Developers
responsible for those classes are class extenders of Game.
Operation parameters and return values are typed in the same way as attributes are. The type
constrains the range of values the parameter or the return value can take. Given an operation, the tuple
made out of the types of its parameters and the type of the return value is called the signature of the
operation. For example, the acceptPlayer() operation of Tournament takes one parameter of type Player
and does not have a return value. The signature for acceptPlayer() is then acceptPlayer(Player):void.
Similarly, the getMaxNumPlayers() operation of Tournament takes no parameters and returns an int.
The signature of getMaxNumPlayers() is then getMaxNumPlayers(void):int.
The class implementor, the class user, and the class extender all access the operations and attributes of
the class under consideration. However, these developers have different needs and are usually not
allowed to access all operations of the class. For example, a class implementor accesses the internal data
structures of the class that the class user cannot see. The class extender accesses only selected internal
structures of superclasses. The visibility of an attribute or an operation is a mechanism for specifying
whether the attribute or operation can be used by other classes or not. UML defines four levels of
visibility:
• A private attribute can be accessed only by the class in which it is defined. Similarly, a private
operation can be invoked only by the class in which it is defined. Private attributes and operations
cannot be accessed by subclasses or calling classes. Private operations and attributes are intended for
the class implementor only.
A protected attribute or operation can be accessed by the class in which it is defined and by any
descendant of that class. Protected operations and attributes cannot be accessed by any other class.
Protected operations and attributes are intended for the class extender.
• A public attribute or operation can be accessed by any class. The set of public operations and
attributes constitute the public interface of the class and is intended for the class user.
• An attribute or an operation with visibility package can be accessed by any class in the nearest
enclosing package. This visibility enables a set of related classes (for example, forming a subsystem) to
share a set of attributes or operations without having to make them public to the entire system.
Visibility is denoted in UML by prefixing the name of the attribute or the operation with a character
symbol: – for private, # for protected, + for public, or ~ for package. For example, in Figure 9-3, we
specify that the maxNumPlayers attribute of Tournament is private, whereas all the class operations are
public.
Contracts: Invariants, Preconditions, and Postconditions
Contracts are constraints on a class that enable class users, implementors, and extenders to share the
same assumptions about the class [Meyer, 1997]. A contract specifies constraints that the class user
must meet before using the class as well as constraints that are ensured by the class implementor and
the class extender when used. Contracts include three types of constraints:
• An invariant is a predicate that is always true for all instances of a class. Invariants are constraints
associated with classes or interfaces. Invariants are used to specify consistency constraints among class
attributes.
• A precondition is a predicate that must be true before an operation is invoked. Preconditions are
associated with a specific operation. Preconditions are used to specify constraints that a class user must
meet before calling the operation.
• A postcondition is a predicate that must be true after an operation is invoked. Postconditions are
associated with a specific operation. Postconditions are used to specify constraints that the class
implementor and the class extender must ensure after the invocation of the operation.
Object Constraint Language
A constraint can be expressed in natural language or in a formal language such as Object Constraint
Language (OCL) [OMG, 2006]. OCL is a language that allows constraints to be formally specified on single
model elements (e.g., attributes, operations, classes) or groups of model elements (e.g., associations
and participating classes). In the next two sections, we introduce the basic syntax of OCL. For a complete
tutorial on OCL, we refer to [Warmer & Kleppe, 2003].
A constraint is expressed as a boolean expression returning the value True or False. A constraint can be
depicted as a note attached to the constrained UML element by a dependency relationship. Figure 9-4
depicts a class diagram of Tournament example of the previous section using UML and OCL.
2. Players can be accepted in a Tournament only if they are already registered with the corresponding
League.
3. The number of active Players in a League are those that have taken part in at least one Tournament of
the League.
An Overview of Mapping:
A transformation aims at improving one aspect of the model (e.g., its modularity) while preserving all of
its other properties (e.g., its functionality). Hence, a transformation is usually localized, affects a small
number of classes, attributes, and operations, and is executed in a series of small steps. These
transformations occur during numerous object design and implementation activities. We focus in detail
on the following activities:
• Optimization: This activity addresses the performance requirements of the system model. This
includes reducing the multiplicities of associations to speed up queries, adding redundant associations
for efficiency, and adding derived attributes to improve the access time to objects.
• Realizing associations: During this activity, we map associations to source code constructs, such as
references and collections of references.
• Mapping contracts to exceptions: During this activity, we describe the behavior of operations when
contracts are broken. This includes raising exceptions when violations are detected and handling
exceptions in higher level layers of the system.
• Mapping class models to a storage schema (Section 10.4.4). During system design, we selected a
persistent storage strategy, such as a database management system, a set of flat files, or a combination
of both. During this activity, we map the class model to a storage schema, such as a relational database
schema.