What is object-oriented programming?
1982
What is object-oriented programming?
My guess is that object-oriented programming
will be in the 1980s/1990s
what structured programming
was in the 1970s.
Everybody will be in favor of it.
Every manufacturer will promote his products as
supporting it.
Every manager will pay lip service to it.
Every programmer will practice it.
And no one will know just what it is.
\zline{untraced quotation}
slide: Object Oriented Programming
Perhaps unfortunately, it is not true that currently every programmer
uses an object-oriented language.
Nor is it true that everybody is in favor of object orientation.
To study object-oriented programming, however, the least one
can do is to become familiar with the terminology employed.
In this section, we will discuss the terminology associated
with object orientation, and look
at what features and benefits are generally acclaimed
by proponents of an object-oriented approach.
Object terminology
Objects provide the means by which to structure a system.
In Smalltalk (and most other object-oriented languages) objects are considered to
be grouped in classes. A class specifies the behavior of the objects that
are its instances. Also, classes act as templates from which actual objects may be created.
Inheritance is defined for classes only.
From the perspective of design, inheritance is primarily meant
to promote the reuse of specifications.
See slide [1-speak].
Object terminology
\zline{\fbox{object speak}}
- objects -- packet containing data and procedures
- methods -- deliver service
- message -- request to execute a method
- class -- template for creating objects
- instance -- an object that belongs to a class
- encapsulation -- information hiding supported by objects
- inheritance -- mechanism allowing the reuse of class specifications
- class hierarchy -- tree structure representing inheritance relations
- polymorphism -- to hide different implementations behind a common interface
slide: Object terminology
The use of inheritance results in a class hierarchy
that, from an operational point of view,
determines the dispatching behavior of objects,
that is what method will be selected in response to a message.
If certain restrictions are met (see sections [contracts],
[subtypes] and [types-behavioral]),
the class hierarchy corresponds to a type hierarchy,
specifying the subtype relation between classes of objects.
Finally, an important feature of object-oriented
languages is their support for polymorphism.
Polymorphism is often incorrectly identified with inheritance.
Polymorphism by inheritance makes it possible
to hide different implementations behind a common interface.
However, other forms of polymorphism may arise by overloading functions
and the use of generic (template) classes or functions.
See sections [1-poly] and [flavors].
Features and benefits of OOP
Having become acquainted with the terminology of OOP,
we will briefly review what are generally considered features
and benefits from a pragmatic point of view.
This summary is based on [Pok89].
I do expect, however, that the reader will take the necessary caution
with respect to these claims.
See slide [1-features].
Both information hiding and data abstraction
relieve the task of the programmer using existing code,
since these mechanisms mean that the programmer's
attention is no longer distracted
by irrelevant implementation details.
On the other hand, the developer of the code (i.e. objects)
may profit from information hiding as well
since it gives the programmer the freedom to optimize
the implementation without interfering with the client code.
Sealing off the object's implementation by means of a well-defined
message interface moreover offers the opportunity
to endow an object with (possibly concurrent)
autonomous behavior.
Features of OOP
- information hiding:
- state, autonomous behavior
- data abstraction:
- emphasis on what rather than how
- dynamic binding:
- binding at runtime, polymorphism \nop{, virtual functions}
- inheritance:
- incremental changes (specialization), reusability
slide: Features of OOP
The flexible dispatching behavior of objects that lends objects
their polymorphic behavior is due to the
dynamic binding of methods to messages.
For the language C++, polymorphic
object behavior is effected by using virtual functions,
for which, in contrast to ordinary functions, the binding to
an actual function takes place at runtime and not at compile-time.
In this way, inheritance provides a flexible mechanism by which to reuse code
since a derived class may specialize or override
parts of the inherited specification.
Apparently, at the current stage of OOP, it is still difficult
to distinguish clearly between what are just features and what
must be regarded as actual benefits.
From a pragmatic viewpoint, OOP offers encapsulation
and inheritance as the major abstraction mechanisms to be used
in program development.
See slide [1-benefits].
Encapsulation promotes modularity, meaning that objects must
be regarded as the building blocks of a complex system.
Once a proper modularization has been achieved, the implementor of
the object may postpone any final decisions concerning the implementation
at will.
This feature allows for quick prototyping, with the risk
that the `quick and dirty' implementations will never be cleaned up.
However, experience with constructing object-oriented libraries
has shown that the modularization achieved with objects
may not be very stable. See chapter 11.
Another advantage of an object oriented approach,
often considered as the main advantage, is the reuse of code.
Inheritance is an invaluable mechanism in this respect,
since the code that is reused seldom offers all that is needed.
The inheritance mechanism enables the programmer to
modify the behavior of a class of objects
without requiring access to the source code.
Benefits of OOP
- OOP = encapsulation + inheritance
- modularity --
autonomous entities, cooperation through exchanges of messages
- deferred commitment --
the internal workings of an object can be redefined without changing other parts of the system
- reusability --
refining classes through inheritance
- naturalness --
object-oriented analysis / design, modeling
slide: Benefits of OOP
Although an object-oriented approach to program development
indeed offers great flexibility, some of the problems it addresses are
intrinsically difficult and cannot really be solved by mechanisms alone.
For instance, modularization is recognized to be a notoriously
difficult problem in the software engineering literature.
Hence, since some of the promises of OOP depend upon the
stability of the chosen modularization, the real advantage of OOP may
be rather short-lived.
Moreover, despite the optimistic claims about `tuning' reused code
by means of inheritance, experience shows that often more understanding
of the inherited classes is needed than is available in their specification.
The probability of arriving at a stable modularization may
increase when shifting focus from programming to design.
The mechanisms supported by OOP allow for modeling application
oriented concepts in a direct, natural way.
But this benefit of OOP will only be gained at the price of increasing
the design effort.
Polymorphism
Polymorphism essentially characterizes the type
of a variable, function or object.
Polymorphism may be due to overloading, parametrized types or inheritance.
Polymorphism due to inheritance is often considered
as the greatest contribution of object-oriented languages.
This may be true, but the importance of generic (template) types
and overloading should not be overlooked.
Overloading
extern void print(int); \fbox{print}
extern void print(float);
Generic class -- templates
template< class T > class list { ... } \fbox{ list<T> }
list<int>* alist;
Polymorphism by inheritance
class shape { ... }; \fbox{ shape }
class circle : public shape { ... }
shape* s = new circle;
slide: Polymorphic type declarations
In slide [1-polymorphism] some examples are given of
declarations involving polymorphic types.
The function print is separately defined for int and float.
Also, a generic list class is defined by means by employing
templates.
The list may be used for any kind of objects, for example integers.
Finally, a shape class is defined from which a circle class
is derived.
An instance of the circle may be referred to by using a shape
pointer, because the type shape encompasses circle objects.
Inheritance and virtual functions
The power of inheritance in C++ comes from virtual
functions and dynamic binding.
Dynamic binding realizes the polymorphism inherent
in the static type structure of the inheritance
hierarchy in a dynamic way.
Dynamic binding is illustrated
by the example shown in slide [7-dispatching].
Virtual functions -- dispatching
class A { \fbox{A}
public:
virtual void operator()() {
cout << "A";
}
};
class B : public A { \fbox{B}
public:
virtual void operator()() {
cout << "B";
}
};
// A* a = new B(); (*a)();
// produces: B
slide: Virtual functions -- dispatching
The class A defines a virtual member function
(that results in printing A) which is redefined
by a similar function in class B (which results
in printing B).
As an example of using the classes defined above,
look at the following program fragment:
A* a = new B(); (*a)();
In case the function would not have been defined as virtual,
the outcome of applying it to (a pointer to) a B object
would have been A, instead of B.
Virtual functions that are redefined in derived classes
may still access the original version defined in the base
class, as illustrated in slide [7-scoping].
Scoping -- explicit
class B : public A { \fbox{}
public:
virtual void operator()() {
cout << "B";
cout << "B";
A::operator()();
}
};
// A* a = new B(); a->A::operator()(); (*a)();
// produces: ABBA
slide: Virtual functions -- scoping
Scoping may be used within a member function of the
class as well as by a client (when invoking a member
function) as illustrated below.
A* a = new B(); a->A::operator()(); (*a)();
The outcome of this statement is ABBA.
Such a scoping mechanism is certainly not unique
to C++, although the notation for it is.
In Smalltalk, the expression super may be used
to access methods defined in the ancestor class,
and in Eiffel one must use a combination of
redefining and renaming to achieve this.
As a remark, I prefer the use of
operator()() when any other method name would
be arbitrary.
The attractiveness of operator()() is
that it is used as a function application
operator, as in (*a)().
Responsibilities in OOP
After this first glance at the terminology and mechanisms
employed in OOP,
we will look at what I consider to be the contribution of OOP
(and the theme of this book) in a more thematic way.
The term `responsibilities' in the title of this section
is meant to refer to an approach to design that has become
known as responsibility driven design
(see Wirfs-Brock, 1989).
Of course, the reader is encouraged to reflect on alternative interpretations
of the phrase responsibilities in OOP.
The approach captured by the term responsibilities
stresses the importance of an abstract
characterization of what services an object delivers,
in other words what responsibilities an object carries
with respect to the system as a whole.
This notion of responsibilities may also be regarded as
the background of contracts as introduced in [Meyer88],
which specify in a precise manner the relation between
an object and its `clients'.
Objects allow one to modularize a system in distinct
units, and to hide the implementation details of
these units, by packaging
data and procedures in a record-like structure and defining a message interface to
which users of these units must comply.
Encapsulation refers to the combination of
packaging and hiding.
The formal counterpart of encapsulation is to be found
in the theory of abstract data types.
An abstract data type (ADT) specifies the behavior of an entity in
an abstract way by means of what are called operations
and observations,
which operationally amount to procedures and functions to change or
observe the state of the entity. See also section [adt-modules].
Abstract data types, that is elements thereof,
are generally realized by employing a hidden state.
The state itself is invisible, but may be accessed and modified
by means of the observations and operations specified by the type.
See slide [1-ADT].
Encapsulation
ADT = state + behavior
Object-Oriented Modeling
slide: Abstract Data Types -- encapsulation
Complex applications involve usually complex data.
As observed by [Wirfs89], software developers have reacted to
this situation by adopting more data oriented solutions.
Methods such as semantic information modeling and
object-oriented modeling were developed to accommodate
this need. See also sections [ood-semantic] and [env-methods].
Objects may be regarded as embodying an (element of an) abstract data type.
To use an object, the client only needs to know what
an object does, not (generally speaking) how
the behavior of the object is implemented.
However, for a client to profit from the data hiding facilities offered by objects,
the developer of the object must provide an interface
that captures the behavior of the object in a sufficiently abstract way.
The (implicit) design guideline in this respect must be
to regard an object as a server
that provides high level services on request
and to determine what services the application requires of that
particular (class of) objects(s).
See slide [1-respons].
Responsibilities
- to specify behavior \zline{\fbox{{\bf what} rather than how}}
client/server model
Client
- makes request to perform a service
Server
- provides service upon request
slide: Responsibilities in OOP
Naturally, the responsibilities of an object cannot
be determined by viewing the object in isolation.
In actual systems, the functionality required
is often dependent on complex interactions between
a collection of objects that must cooperate in order
to achieve the desired effect.
However, before trying to specify these interactions,
we must indicate more precisely
how the communication between a server and a single
client proceeds.
From a language implementation perspective, an object
is nothing but an advanced data structure, even when we
fit it in a client-server model.
For design, however, we must shift our perspective
to viewing the object as a collection of high level,
application oriented services.
Specifying the behavior of an object from this perspective,
then, means to define what specific information the object is responsible
for and how it maintains the integrity of that information.
See slide [1-contracts].
object = information + responsibilities
Contracts
Behavioral refinement
slide: Contracts and behavioral refinement
The notion of contracts was introduced by [Meyer88]
to characterize in a precise manner what services an object must
provide and what requirements clients of an object must meet
in order to request a service (and expect to get a good result).
A contract specifies both the requirements imposed on a client and the obligations
the server has, provided the requirements are met.
When viewed from the position of a client, a contract reveals
what the client can count on when the requirements are fulfilled.
From the position of the server, on the other hand,
when a client does not fulfill the requirements imposed,
the server has no obligation whatsoever.
Formally, the requirements imposed on the client
and the obligations of the server
can be specified by means of pre- and post-conditions
surrounding a method.
Nevertheless, despite the possibility of formally verifying
these conditions, the designer must specify the right
contract for this approach to work at all.
A problem of a more technical nature the designer
of object-oriented systems faces is how to deal with
inheritance.
Inheritance, as a mechanism of code reuse,
supports the refinement of the
specification of a server.
From the perspective of abstract data types, we must require
that the derived specification refines the behavior of the original server.
We must answer the following two questions here.
What restrictions apply, when we try to refine the behavior of a server
object?
And, ultimately, what does it mean to improve a contract?
Behavioral refinement
Inheritance provides a very general and powerful mechanism for reusing code.
In fact, the inheritance mechanism is more powerful
than is desirable from a type-theoretical perspective.
Conformance -- behavioral refinement
if B refines A
then
B may be used
wherever A is allowed
slide: Behavioral refinement
An abstract data type specifies the behavior of a collection of entities.
When we use inheritance to augment the definition of a given type,
we either specify new behavior in addition to what was given,
or we modify the inherited behavior, or both.
The restriction that must be met when modifying behavior is that the
objects defined in this way
are allowed to be used at all places where objects of the given type
were allowed.
This restriction is expressed in the so-called
conformance rule that states that {\em
if B refines A then B may be used wherever A is allowed}.
Naturally, when behavior is added, this condition is automatically
fulfilled.
See slide [1-conformance].
The conformance rule gives a very useful heuristic for applying inheritance safely.
This form of inheritance is often called `strict' inheritance.
However, it is not all that easy to verify that a class
derived by inheritance
actually refines the behavior specified in a given class.
Partly, we can check for syntactic criteria such as the signature
(that is, type) of the individual methods, but this is definitely not
sufficient.
We need a way
in which to establish that the behavior (in relation to a possible)
client is refined according to the standard introduced above.
In other words we need to know how to improve a contract.
Recall that
from an operational point of view an object
may be regarded as containing data attributes storing information
and procedures or methods representing services.
The question {\em "how to improve a contract?"}
then boils down to two separate questions,
namely: (1) {\em "how to improve the information?"}
and (2) {\em "how to improve a service?"}.
To provide better information is, technically speaking,
simply to provide more information, that is more specific
information.
Type-theoretically, this corresponds to narrowing down the possible
elements of the set that represents the (sub) type.
To provide a better service requires either relieving
the restrictions imposed on the client
or improving the result, that is tighten the obligations of the server.
Naturally, the or must be taken as non-exclusive.
See slide [1-services].
Attributes
Services
Contracts
A better service
- Fewer restrictions for the client
- More obligations for the server
slide: Improving services
To improve a contract thus simply means adding more services
or improving the services that are already present.
As a remark,
[Meyer88] inadvertently uses the term subcontract
for this kind of refinement.
However, in my understanding, subcontracting is more a process
of delegating parts of a contract to other contractors
whereas refinement, in the sense of improving contracts,
deals with the contract as a whole,
and as such has a more competitive edge.
Summarizing, at a very high level we may think of objects
as embodying a contract.
The contract is specified in the definition of the class
of which that object is an instance.
Moreover, we may think of inheritance as a mechanism to effect
behavioral refinement,
which ultimately means to improve the contract defining
the relation between the object as a server and a potential client.
Object-Oriented Modeling
- prototyping, specification, refinement, interactions
OOP = Contracts + Refinements
slide: Object-oriented modeling
To warrant the phrase contract, however, the designer of an object
must specify the functionality of an object in a sufficiently
abstract, application oriented way.
The (implicit) guideline in this respect is to construct a model
of the application domain.
See slide [1-modeling].
The opportunity offered by an object-oriented approach
to model concepts of the application domain
in a direct way makes an object-oriented style suitable
for incremental prototyping
(provided that the low-level support is available).
The metaphor of contracts provides valid guidelines for the
design of objects.
Because of its foundation in the theory of abstract data types,
contracts may be specified (and verified) in a formal way,
although in practice this is not really likely to occur.
Before closing this section, I wish to mention
a somewhat different interpretation of the notion
of contracts which is proposed by [HHG90].
There contracts are introduced to specify the behavior of collections
of cooperating objects.
See section [formal-coop].