Lecture Notes on Object-Oriented Programming

The Quality of Classes and OO Design

This collection of notes on OOP was never meant to stand alone. It also represents a view of OO circa early to mid 1990s. Some people still find them useful, so here they are, caveat emptor. Special thanks to Gilbert Benabou for taking to time to compile the first printable version of this document and inspiring us to provide it.
[PDF]
Printable Version

The concept of type is more or less important in a language, depending on whether the language is strongly or weakly (or not at all) typed.

In strongly typed languages, the compiler prevents you from mixing different kinds of data together. This is limiting, but can be very helpful when you want to avoid putting a string, say, into the floating point value for the altitude of an airplane's automatic pilot.

In OO languages, type is a synonym for class, at least as a first cut. In fact, class and type are quite distinct. A more nuanced understanding of type is one of the things that distinguishes a shallow from a deep understanding of OO design and programming.

Type defines the properties and behaviors shared by all objects of the same type (class).

OO languages can run the range of un-typed, weakly typed, or strongly typed.

The advantage of strongly typed languages is that the compiler can detect when an object is being sent a message to which it does not respond. This can prevent run-time errors. The other advantages of strong typing are:

  • earlier detection of errors speeds development
  • better optimized code from compiler
  • no run-time penalty for determining type

The disadvantages of strong typing are:

  • loss of some flexibility
  • more difficult to define collections of heterogenous objects

C++ maintains strong class typing. An object may only be sent a message containing a method that is defined in the object's class, or in any of the classes that this object inherits from. The compiler checks that you obey this. Objective C support strong typing, but it also allows for weak typing. A special type, id, can be used for object handles. An id can be a handle to any class of object, thus it has no typing information, and the compiler can't check if you are sending proper messages. This is done instead at run-time.

Type vs Class

One of the best pieces of advice that can be given for OOD is to code to types, not classes. This follows from the general advice of postponing obligation. By writing to an interface, the decision about which classes will actually be used is postponed. It becomes impossible to write in dependencies on any particular class. It becomes possible in the future to plug new and unanticipated objects into the same code structure. Flexibility is preserved.

A class is two things: implementation and interface. The interface part is the only part that affects the type. The implementation part is just messy detail.

We can code carefully to a type using only the interface portion of a given class. We still have the problem that in the future we might need to add a new class of the same type, and our code is written with specific classes - time for rewriting. And if we don't code very carefully we might find that some class-specific things have crept into our code. In other words, we didn't stick strictly to the interface.

The better alternative is to be able to separately define an interface as its own full-fledged, supported, OO language abstraction. A class that had only interface (and no implementation) would be a type. It would be impossible for the code we write to this type to have any reliance on the specific implementation found in a class. And, as a bonanza, we have drastically decreased the amount of code that has to be re-written in the future when we want to add a new class, since we've coded to the interface/type, and we can simply plug objects of the new classes that are of the same type into our code.

The Venn diagram below shows three classes and two interfaces. It demonstrates the idea of substitutability as defined on type (interface), not class. A and B are both substitutable for type T0. B and C are substitutable for type T1.

Note that class A may be more than T0, in other words, it can have other aspects of its interface than what T0 specifies.

OO languages have different ways of representing type.

Interface in Java

Java lets you define an interface. For example:

interface Device {
 boolean init();
 void start();
 void stop();
}

Interfaces don't get work done, objects do. Objects need to have a class definition. You can't have "interface objects". Instead, a Java class implements an interface, meaning it has implementations for the methods in the interface.

class Sensor implements Device {
 boolean init() { ... }
 void start() { ... }
 void stop() { ... }
}

Java lets us program to an interface by declaring variables of the type. Consider this fragment of client code:

Device dev = new Sensor();
dev.init();
dev.start();

Now consider what happens someday when we want to plug a new type of device into our client. The only line of code that changes is the line where we specifically instantiate the Sensor object. All the other code is written to use the Device interface variable @dev@.

Interface in C++

In C++, we don't have an interface keyword, but we can achieve the same thing of a type which is pure interface. Unfortunately we do this by using the class abstraction:

class Device {
 virtual boolean init() = 0;
 virtual void start() = 0;
 virtual void stop() = 0;
};

The odd looking syntax (initializing a method signature to zero?!) is what is known as a "pure virtual method". A pure virtual method has no implementation. A class with one or more pure virtual methods is abstract - you can't create objects from it. Subclasses of a class with a pure virtual method are required to implement that method, or be abstract themselves.

class Sensor : public Device {
 boolean init();
 void start();
 void stop();
};
boolean Sensor::init() {
 ...
}
void Sensor::start() {
 ...
}
void Sensor::stop() {
 ...
}

C++ lets us program to an interface by declaring variables of the abstract class type. Consider this fragment of client code:

Device dev = new Sensor();
dev.init();
dev.start();

UML Representation

There are two means of showing an interface in UML. The first is via a full stereotype (with all possible compartments and elaborations). The second is with a simple circle and the interface name.