Thomas A. Alspaugh
Object Orientation

Under Construction

OO Examples.

What Is Object Orientation?

Object-orientation is a way of organizing a view of a world.

Object-orientation is also a way of organizing software. The software view is made isomorphic to the view of the world.

Objects and values

In an object-oriented view, a world is composed of two kinds of entities, values and objects. Let's use numbers as example values and cars as example objects.

A value is eternal, always there, never changing, and unique.

4 is always there; we don't have to create a new 4 when we need one, and in fact we can't create a new 4 (it doesn't even make sense to talk about creating another 4). 4 is the same today as it was yesterday, and as it will be tomorrow. And 4 is unique: there's only one 4. You may ask, What about the roman numeral IV? But that's just another name for 4 (as is four, 1002, (1+3), √16, and an infinite number of other ways of saying it).

An object is everything a value isn't: an object is temporal, has to be created before it can exist and can be destroyed when no longer needed, can change over time, and can be copied and exist simultaneously in several copies.

A car is not always there; it has to be created (at the factory) before it exists, and someday it will be junked and stop existing. A car can change over time: its mileage counts up, it can be repainted or get new tires, it can develop a strange clunking noise. A car is not unique; the factory made many cars just like this one.

Operations on objects are provided by the objects themselves.

Objects inherit

ontology

Figure 1. An ontology of cars

All the objects in a world are organized in a hierarchical classification—an ontology—in which the kinds of characteristics displayed by objects higher in the hierarchy are inherited by every object descended from them in the hierarchy.

One way to view this (not the only way) is to organize objects into classes, such that all objects in a class are alike, and classes are organized into a subsumption or inheritance hierarchy such that each class inherits all the characteristics of its parent class (and so on up and down the hierarchy). Each class has a superclass, its (sole) parent; each class may have one or more subclasses, its children.

Cars (Fig. 1). A coupé has all the characteristics of a sedan (an enclosed automobile), and also all those of a car. A convertible presumably has characteristics not shared by a sedan.

This kind of object-orientation is class-based and single-inheritance; Java uses this model.

Multiple inheritance is also possible, in which each class may have two or more parents and inherit characteristics from all of them. With multiple inheritance it is necessary to have some mechanism for resolving conflicts between characterics inherited from different parents. C++ uses this model.

Finally, classes are not necessary; rather than constructing a new object by calling a class's constructor, each object can act as a template from which new objects like it are constructed by copying, and new categories of objects can be created by changing the characteristics of an object to form a new template. Javascript uses this model.

In all these cases, inheritance is defined at the programming language level in terms of inheritance of attributes and methods. Each object inherits the attributes and methods defined by all its ancestors, whether those ancestors are classes or object templates. If two or more ancestors define methods with the same signature (method name and parameter types), the definition by the closest ancestor is used; at every level of inheritance, more distant method definitions can be overwritten by new ones.

Objects are polymorphic

Each object is also not only an instance of its immediately-containing category, but also of all its supercategories. In class-based object orientation, classes are these categories (as are interfaces, for languages that define them). In Figure 1's terms, a car instance that is a Four-door is also a Sedan, and whenever a Sedan is expected that car instance can be used. This is polymorphism, one object appearing in many guises, its class and superclasses.

In template-based object orientation, the categories are not explicit but are determined by what methods and attributes each object has. An object is polymorphic there in that any object with a method named foo() can appear in a context that calls foo().

Objects are not passive

In an object-oriented view, actions are not done on objects; the objects do the acting, when requested. Each object has a set of methods that perform all of the actions and transformations in which the object is capable of participating.

Actions involving cars. While you may think of yourself starting the car, in an object-oriented view you would use the car's start method to cause the car to start itself.

In this way, you don't need to know the differences between what your gas-powered Honda does when you start it, what your new plug-in electric Tesla sports car does when you start it, and what that mysteriously-powered hovercraft does when you start it in a computer game.

Objects have state

Each object has (or can have) a state that gives its present condition and summarizes its history. This state is represented by the values of the object's attributes (or fields).

Good object-oriented practice is to have the values private to the class, so that they are set only by the methods (which you write). The methods are written to ensure that the attributes represent a consistent state (all attributes have values that can go together meaningfully), and the state appropriate for the object.

Objects are typically designed so that their state (or a subset of it) is visible outside the object; but the visible state need not be divided up as the object's attributes divide it up (although it often is anyway). The methods that present the state can do so in any form the designer wishes.

Objects hide what's inside

This, also termed encapsulation, falls into two parts.

  1. Object-oriented languages give the ability to control access to each object's state, so that the form of the state (how it is divided into attributes) and part or all of the state itself can be hidden from outside observation.
  2. The implementation of an object's methods is hidden behind the object's interface.

Consequently, a class's developer can design and implement it to optimize any desired goal: to make it as time-efficient as possible, or to make it as resistant to misuse as possible, or — the most general goal — to hide an implementation decision in order to isolate other modules from changes to that decision.

How Does Object Orientation Help Software Development?

The benefits fall into two major categories: making design easier, and supporting developmental-quality goals.

The shape of the design is the shape of the world

In order to develop a system, one must first understand the world the system will participate in.

A spreadsheet program participates in a world in which the primary entities are spreadsheets, arrays of cells each of which can contain (simplifying for brevity) either a number, which is the result of that cell, or a formula that can operate on the results of other cells to calculate the result of its cell.
A thermostat program participates in a world in which there is a temperature sensor, a clock, possibly other sensors, one or more devices that heat or cool, and (conceptually) some regimes of temperature control that exist or will exist in the minds of its users.

Object oriented design primarily organizes the software as its world is organized. The states and methods of each class are chosen to appropriately represent the states of entities in the software's world, and the operations that can take place involving those entities. This world comprises entities that are physical (like clocks) but also entities that are conceptual (like formulas and temperature regimes).

The object-oriented spreadsheet program has a class of cells, as does its world. The cell class could plausibly have three subclasses for (respectively) empty cells, cells containing a number, and cells containing a formula, again paralleling its world. The cell class would have a method that returned the cell's result, and each of the three subclasses would probably implement that method differently but satisfy the same method description. There is plenty more to decide about this design, but its outline is probably not going to change unless the world of spreadsheets it inhabits changes.
The object-oriented thermostat program has a class for temperature sensors (probably with a single object), a class for clocks (again probably single), a class for each other kind of sensor, and a class for heating or cooling devices subclassed into one class for each type. The temperature sensor class has a method that returns the temperature; the clock class has a method that returns the time; the heating/cooling device class has a method that commands the device to achieve a specific temperature. Depending on the temperature regimes that are projected for this world, there will probably be a class for all temperature regimes, probably making use of the clock, temperature sensors, and whatever other kinds of sensors are present and controlling all the heating/cooling devices that are present.

In addition to the world providing a shape for the software design, the software design (and later its implementation) acts as a verification that the model of the world is consistent and (within its boundaries) complete, and helps validate that the model is an appropriate one for the goals desired in this world.

Of course, modelling such a world appropriately is not a simple process; it's hard work and difficult to get right. But it's going to be hard work and difficult to get right anyway, regardless of how you do the design.

Information hiding

Object-orientated languages can provide strong support for information hiding, the approach for localizing the effect of changes. The principle of information hiding is to identify the design decisions that are most likely to change, then encapsulate each of these behind an interface that will be appropriate for all the likely choices. Then whenever one of those decisions changes, only the code behind its interface needs to be altered; all the rest of the system acts on it through the (constant) interface, and does not know what choice is currently in effect.

The language support is provided by the possibility of writing methods that control access to an object, and of preventing all other access except through those methods.

Type-appropriate actions

Each class can act as an abstract data type that defines a domain of objects of the type and the operations on those objects. A good object-oriented design allows only appropriate actions to take place. Object-oriented languages provide facilities for limiting the available actions both statically, in terms of a set of methods provided for a class, and dynamically, in terms of implementation support for preventing operations inappropriate for an object's state at that instant.

Reuse-ready modules

Brooks quotes Parnas on reuse:

Reuse is something that is far easier to say than to do. Doing it requires both good design and very good documentation. Even when we see good design, which is still infrequently, we won't see the components reused without good documentation.

The Mythical Man-Month (1995), page 224, quoting Parnas.

The combination of object orientation, open source software, and Google search has appeared to result in a marked increase in software reuse. In part this is due to the strong encapsulation provided by classes; it is easy to hide the internals of each class from all other classes, and as a result an individual class (or package of related classes) is more likely to form a separate unit that is independent of its context. The existence of tools like javadoc that extract and format interface information probably contributes as well, encouraging developers to provide at least a minimum of documentation.

The Challenges of Polymorphism and Inheritance

Polymorphism and inheritance give expressive and conceptual power, but also impose semantic burdens that have to be met by the programmer.

An object of a subclass must act like an object of the superclass

Polymorphism imposes this burden. A language with classes and polymorphism allows an object of class C that is a subclass of D to be used at run time whenever a reference of class D appears at compile time. That means that anything that can be expected of a D has to be supplied by a C. Presumably a C has some additional behaviors and subtleties beyond what a D provides (otherwise why have C at all?), but in addition a C has to be able to be a D under any circumstances.

Inheritance (of attributes and methods) helps somewhat, but isn't sufficient by itself, since expectations have also been inherited and those expectations have to be satisfied by the subclass's added or overwritten methods and attributes. The responsibility falls on the programmer who writes the subclass. The designer and programmer of the superclass also have an effect, since the superclass should be designed and its interface specified in a way that can be sensibly inherited, but either way the programmer who writes the subclass has to make it happen.

An inherited method introduces a dependence

In order to evolve a class with one or more methods inherited by a subclass, the programmer must know enough about the subclass's implementation to not break its interface. This introduces a dependence between the two classes, so that the programmer of the parent class must know about the implementation of the child class, with all the usual complications: the programmer has to know more, and has to keep up with changes in what [s]he knows about the child class; information hiding by the child class is violated since now changes to the parent class may be restricted by the requirements of the child class; and so forth.

An overwritten method introduces a dependence

In order to evolve a class that overwrites a method of a parent class, the programmer must know enough about the parent class's implementation to safely satisfy its interface as well as the evolved class. The greatest risk is that the parent class may call the overwritten method from inside one or more of its other methods; indeed, such calls are common for object oriented programming. This introduces a dependence in the opposite direction between the two classes, so that the programmer of the child class must know about the implementation of the parent class, again with all the usual complications.

Polymorphism without inheritance

Inheritance implies polymorphism in most languages, but polymorphism and its advantages can be obtained without inheritance in many of them. In Java, for example, polymorphism can be achieved through the use of interfaces. An interface specifies a set of method signatures, and any class that implements those method signatures can be used polymorphically as an instance of that interface. Such classes need have no inheritance relationship and thus are not troubled by dependencies through inheritance.

Of course such classes still have behavioral dependencies, through their common interface that they all implement not only by providing the necessary signatures but also by providing the necessary behavior specified for those signatures. But that dependence is inseparable from the advantages of polymorphism, and is the expected price for them.

Delegation as an alternative to inheritance

If one desires to share method implementation code without inheritance, one approach is to delegate it to a specialized class D that has those method implementations. Then all classes whose methods are to share that code implement their methods by simply calling the appropriate methods of D.

For example, in order to share code implementing method m() between classes C1 and C2 both implementing interface I, place the implementation of m() in class D as a static method taking the caller's this as a parameter, then implement C1 and C2's m() simply as

  returnType m() {  return D.m(this);  }

Special Cases

Unique objects

It is possible for objects to be unique, like values. There are two senses in which this can be done.

Distinguished by their address

Most object-oriented languages provide a way of comparing the memory address of two objects; this will distinguish two otherwise-identical objects.

Singletons

The Singleton design pattern shows one way of implementing a class so that there is only one, unique, object of that class.

Classes with no objects

It is possible to design a class that has no objects, only static methods and (perhaps) static constants. The class then is simply an organizing unit for its methods and constants.

An example is the java.util.Collections class, which provides static constants for empty collections (lists, maps, and sets) and some three dozen static methods for useful operations on various kinds of collections.

The methods of such a class have to be static since there are no objects of the class for methods to operate upon.

To ensure that no objects can be constructed, the class's default constructor (the one with no parameters) is made private, and no other constructors are defined.

Objects without state

Of course it is possible to construct an object that has no attributes and thus no state.

Objects that are always there

There are two senses in which this can be more-or-less achieved.

Static objects

Most object-oriented languages provide a facility for constructing objects at or near program startup. In Java, for example, you can define a class's static block and place in it code to be executed before anything else in the class is accessed.

Persistent objects

Some object-oriented languages provide a facility for saving the state of an object in a form that can be read in and restored when the program (or another one set up for the same classes) reads that state. In Java, for example, this is done through object serialization.

flip bgunflip