In design, objects are our primary unit of decomposition. In our design, objects may reflect real life entities, such as Employer and Employee, but may also represent system artefacts, such as stacks or graphics.
In actual development, that is in the implementation, objects are our unit of implementation. Each object itself may be regarded as a collection of functions. But it is the collection of functions, and the behavior that they describe, that we take as our unit; not the individual function.
A consequence of adopting an object-oriented approach is that we have to spend more time in describing and understanding the structure and behavior of the system, and to learn the formalisms and tools that enable us to do so.
To master this complexity, we need to think about how objects can be made to fit together. To benefit from an object-oriented approach, we need to design a software architecture that defines and regulates the interactions between objects.
A framework is a kind of library of reusable objects. However, in contrast with ordinary software libraries, frameworks may at times take over control. The best-known examples of frameworks are in the GUI domain; frameworks in other domains (e.g. the business process domain) are emerging.
Using a framework may simplify your life, since a framework provides generic solutions for a particular application domain. But the price you pay is twofold. You have to understand what (patterns of) solutions the framework provides, and you have to comply with the rules of the game imposed by the framework.
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.
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 polymorphism and
Features and benefits of OOP
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 and frameworks has shown that the modularization achieved with objects may not be very stable.
Another advantage of an object oriented approach, often considered to be 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.
OO = encapsulation + inheritance
benefits of OOP
Despite the fact that our basic mathematical model of a computing device (and hence our notion of computability) has not altered significantly, the development of high level programming languages has meant a drastic change in our conception of programming. Within the tradition of imperative programming, the introduction of objects, and object-oriented programming, may be thought of as the most radical change of all. Indeed, at the time of the introduction of Smalltalk, one spoke of a true revolution in the practice of programming.
the object model
In the (ordinary) sequential machine model, the result of a computation is (represented by) the state of the machine at the end of the computation. In contrast, computation in the object model is best characterized as cooperation between objects. The end result then consists, so to speak, of the collective state of the objects that participated in the computation. See slide 2-model. Operationally, an object may be regarded as an abstract machine capable of answering messages. The collection of messages that may be handled by an object is often referred to as the protocol obeyed by the object. This notion was introduced in the Smalltalk programming environment originally to provide the means to group the messages to which an object may respond. For instance, the distinction between methods for initialization and methods for modification or processing may be convenient in developing or using a program. The notion of protocol may also be given a more formal interpretation, as has been done for instance in the notion of contracts (introduced in Eiffel) stating the requirements that must be adhered to in communicating with an object.
Structurally, an object may be regarded as a collection of data and procedures. In principle, the data are invisible from the outside and may be manipulated only by invoking the right procedure. In a pure object-oriented language such as Smalltalk and Eiffel, sending a message to an object is the only way of invoking such a procedure. Combined, data-hiding and message interface abstraction will be referred to as encapsulation. Actually, object-oriented languages, while in some way supporting objects as collections of data and procedures, may differ subtly in the degree and way in which they support data-hiding and abstraction.
For now, we need not be concerned with the precise
mathematical details of our model of a computing device.
For a very much more precise and elaborate description
of the Turing machine, the interested reader is referred
An interesting, but perhaps somewhat distressing, feature
of the Turing machine model is that it is the strongest model
we have, which means that any other model
of computation is at best equivalent to it.
Parallel computation models in effect do extend the power
of (sequential) Turing machines, but only in a linear
relation with the number of processors.
In other words, the Turing machine defines what we may regard
as computable and establishes a measure
of the complexity of a computation, in space and time.
The awareness of the intrinsic limitations imposed
by a precise mathematical notion of computability
has, for example, led us to regarding the claims of artificial intelligence
with some caution, see
An equally important feature of the Turing machine model is that it gives us an illustration of what it means to program a computing device, that is to instruct the machine to perform actions dependent on its input and state. As an extension to the model, we can easily build a universal computing device, into which we may feed the description of some particular machine, in order to mimic the computation of that machine. Apparently, this gives us a more powerful machine. However, this has proven not to be the case. Neither does this universal device enlarge the class of computable problems, nor does it affect in any significant sense the computational complexity of what we know to be computable. See slide 2-devices.
Object-oriented programming does not enlarge the class of computable problems, nor does it reduce the computational complexity of the problems we can handle.
Our model of a computing device does quite precisely delimit the domain of computable problems, and gives us an indication of what we can expect the machine to do for us, and what not. Also, it illustrates what means we have available to program such a device, in order to let it act in the way we want. Historically, the Turing machine model may be regarded as a mathematical description of what is called the Von Neumann machine architecture, on which most of our present-day computers are based. The Von Neumann machine consists of a memory and a processor that fetches data from the memory, does some computation and stores the data back in memory. This architecture has been heavily criticized, but no other model has yet taken its place. This criticism has been motivated strongly by its influence on the practice of programming. Traditionally, programs for the Von Neumann architecture are conceived as sequences of instructions that may modify the state of the machine. In opposition to this limited, machine-oriented view of programming a number of proposals have been made that are intended to arrive at a more abstract notion of programming, where the machine is truly at the service of the programmer and not the other way around.
One of these proposals to arrive at a more abstract notion of programming is advocated as the object-oriented approach. Before studying the intrinsics of the object-oriented approach, however, it may be useful to reflect on what we may expect from it. Do we hope to be able to solve more problems, or to solve known problems better? In other words, what precisely is the contribution of an object-oriented approach?
Based on the characterization of a computing device,
some answers are quite straightforward.
We cannot expect to be able to solve more problems,
nor can we expect to reduce the computational complexity
of the problems that we can solve.
What an object-oriented approach can contribute,
however, is simply in providing better means
with which to program the machine.
Better means, to reduce the chance of (human)
errors, better means, also, to manage the complexity
of the task of programming (but not to reduce
the computational complexity of the problem itself).
In other words, by providing abstractions that
are less machine oriented and more human oriented,
we may enlarge the class of problems that we can
tackle in the reality of software engineering.
However, we simply cannot expect that an object-oriented
approach may in any sense enlarge our notion
of what is computable.
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 are aimed at providing ways in which to capture the mathematical or logical meaning of a program more directly. These developments have resulted in a number of functional programming languages (e.g. ML, Miranda) and logic programming languages, of which Prolog is the best-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 (e.g. 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, C++ (to a certain extent) and Java,
more clearly reflect the concern with abstraction and verification,
which intrinsically belongs to the notion of abstract data types
as supported by these languages.
Design by Contract
After this first glance at the terminology and mechanisms
employed in object-oriented computation,
we will look at what I consider to be the contribution of an
(and the theme of this book) in a more thematic way.
The term `contract' in the title of this section
is meant to refer to an approach to design that has become
known as design by contract, originally introduced in
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.
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) object(s). See slide 1-respons.
object = information + responsibilities
if B refines A then B may be used wherever A is allowed
OOP = Contracts + Refinements
draft version 0.1 (15/7/2001)