Under Construction
always there
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.
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.
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.
Operations on objects are provided by the objects themselves.
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.
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.
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().
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.
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.
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.
This, also termed encapsulation, falls into two parts.
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.
The benefits fall into two major categories: making design easier, and supporting developmental-quality goals.
In order to develop a system, one must first understand the world the system will participate in.
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).
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.
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.
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.
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.
Polymorphism and inheritance give expressive and conceptual power, but also impose semantic burdens that have to be met by the programmer.
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.
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.
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.
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.
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); }
It is possible for objects to be unique, like values. There are two senses in which this can be done.
Most object-oriented languages provide a way of comparing the memory address of two objects; this will distinguish two otherwise-identical objects.
The Singleton design pattern shows one way of implementing a class so that there is only one, unique, object of that class.
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.
Of course it is possible to construct an object that has no attributes and thus no state.
always there
There are two senses in which this can be more-or-less achieved.
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.
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.