What is object-orientation?

Prior to any other characterization, it must be stressed that object-oriented programming does not deal with programming in the sense of developing algorithms or data structures, but must be studied as a (collection of) means for the organization of programs and, more generally, techniques for designing programs. From this perspective, OOP may be regarded as an emerging methodology. See slide 1-objects. As the primary means for structuring a program or design, OOP provides objects. Objects may model real life entities, may function to capture abstractions of arbitrary complex phenomena, or may represent system artifacts such as stacks or graphics. Operationally, objects control the computation. From the perspective of program development, however, the most important characteristic of objects is not their behavior as such, but the fact that the behavior of an object may be described by an abstract characterization of its interface. Having such an abstract characterization suffices for design. The actual behavior of the object may be implemented later and refined according to need.

OOP -- objects and inheritance

Objects -- abstractions

Inheritance -- incremental development


slide: Objects and inheritance

Perhaps the most important contribution of object orientation to the practice of programming is the use of inheritance to specify relations between (classes of) objects. Inheritance provides a means to add functionality to a specification incrementally. Inheritance thus enables better conceptual modeling, since it allows a factoring out of the common parts of a specification and the reuse of specifications. When used in a disciplined fashion, inheritance allows stepwise refinement of the specification of the type of a class of objects, and it allows us to view different objects of different types as belonging to a common (super) type.

Some history

In the last few decades, we have been able to witness a rapid change in the technology underlying our computer systems. Simultaneously, our ideas of how to program these machines have changed radically as well. The history of programming languages may be regarded as a progression from low level constructs towards high level abstractions, that enable the programmer to specify programs in a more abstract manner and hence allow problem related abstractions to be captured more directly in a program. This development towards high level languages was partly motivated by the need to be able to verify that a program adequately implemented a specification (given in terms of a formal description of the requirements of an application). Regarded from this perspective, it is then perhaps more appropriate to speak of a progression of paradigms of programming, where a paradigm must be understood as a set of mechanisms and guidelines telling us how to employ these mechanisms. The first abstraction mechanism beyond the level of assembler language and macros is provided by procedures. Procedures play an important role in the method of stepwise refinement introduced by the school of structured programming. Stepwise refinement allows the specification of a complex algorithm gradually in more and more detail. Program verification amounts to establishing whether the implementation of an algorithm in a programming language meets its specification given in mathematical or logical terms. Associated with the school of structured programming is a method of verification based on what has become known as Hoare logic, which proceeds by introducing assertions and establishing that procedures meet particular pre- and post-conditions. Other developments in programming language research aimed at providing ways in which to capture the mathematical or logical meaning of a program more directly. These developments resulted in a number of functional programming languages (e.g. ML, Miranda) and logic programming languages, of which Prolog is the most well-known. The programming language Lisp may in this respect also be regarded as a functional language. The history of object-oriented programming may be traced back to a concern for data abstraction, which was needed to deal with algorithms that involved complex data structures. The notion of objects, originally introduced in Simula (Dahl and Nygaard, 1966), has significantly influenced the design of many subsequent languages,(eg. CLU, Modula and Ada). The first well-known object-oriented language was Smalltalk, originally developed to program the Dynabook, a kind of machine that is now familiar to us as a laptop or notebook computer. In Smalltalk, the data hiding aspect of objects has been combined with the mechanism of inheritance, allowing the reuse of code defining the behavior of objects. The primary motivation behind Smalltalk's notion of objects, as a mechanism to manage the complexity of graphic user interfaces, has now proven its worth, since it has been followed by most of the manufacturers of graphic user interfaces and window systems. Summarizing, from a historical perspective, the introduction of the object-oriented approach may be regarded as a natural extension to previous developments in programming practice, motivated by the need to cope with the complexity of new applications. History doesn't stop here. Later developments, represented by Eiffel and to a certain extent C++, more clearly reflected the concern with abstraction and verification, which intrinsically belongs to the notion of abstract data types as supported by these languages.

Object speak

Smalltalk may be held responsible for the initial popularity of the object-oriented approach. However, the terminology it introduced was somewhat unfamiliar, and for many evidently hard to grasp. See slide 1-oo-prog.

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{B'}
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

  • Abstract Data Types

ADT = state + behavior

Object-Oriented Modeling

  • data oriented

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

  • a set of services

Behavioral refinement

  • improving contracts

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

  • more information

Services

  • better services

Contracts

  • more and better services

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.