Criteria of Object Orientation
Notes from Chapter 2, Object Oriented Software Construction By Bertrand Meyer
All credits for the below to Bertrand Meyer, this is just my personal notes, which are 90%+ copied word by word from his textbook Object Oriented Software Construction.
This article will provide a concise listing of the criteria that makes a system object-oriented. We group the criteria into three sections — method and language, implementation and environment, and libraries.
Method and Language
An object-oriented language and environment, together with the supporting method, should apply to the entire lifecycle, in a way that minimises the gaps between successive activities.
The method and language should have the notion of class as their central concept. Informally, a class is a software element describing an abstract data type and its partial or total implementation. An abstract data type is a set of objects defined by the list of operations, or features, applicable to these objects, and the properties of these operations.
The language should make it possible to equip a class and its features with assertions (preconditions, postconditions and invariants), relying on tools to produce documentation out of these assertions and, optionally, monitor them at run time.
Classes as Modules
In a pure object-oriented approach, classes should be the only modules such that there is no notion of main program, and subprograms do not exist as independent module units, but rather appear as part of classses.
Classes as Types
The notion of class is powerful enough to avoid the need for any other typing mechanism. Every type should be based on a class.
In object-oriented computation, there is only one basic computational mechanism: given a certain object, which (because of the previous rule) is always an instance of some class, call a feature of that class on that object.
It should be possible for the author of a class to specify that a feature is available to all clients, to no client, or to specified clients. An immediate consequence of this rule is that communication between classes should be strictly limited. In particular, a good object-oriented language should not offer any notion of global variable; classes will exchange information exclusively through feature calls, and through the inheritance mechanism.
The language should provide a mechanism to recover from unexpected abnormal situations.
A well-defined type system should, by enforcing a number of type declaration and compatibility rule, guarantee the run-time type safety of the systems it accepts. This requires every entity is explicitly declared as being of a certain type, derived from a class. Assignment and argument passing are subject to conformance rules, based on inheritance, which require the source’s type to be compatible with the target’s type.
For typing to be practical, it must be possible to define type-parameterized classes, known as generic. A generic class LIST [G] will describe lists of elements of an arbitrary type represented by G, the “formal generic parameter”; you may then declare specific lists through such derivations as LIST [INTEGER] and LIST [WINDOW], using types INTEGER and WINDOW as “actual generic parameters”. All derivations share the same class text.
It should be possible to define a class as inheriting from another. It should be possible for a class to inherit from as many others as necessary, with an adequate mechanism for disambiguating name clashes.
The combination of genericity and inheritance brings about an important technique, constrained genericity, through which you can specify a class with a generic parameter that represents not an arbitrary type as with the earlier (unconstrained) form of genericity, but a type that is a descendant of a given class.
A generic class SORTABLE_LIST, describing lists with a sort feature that will reorder them sequentially according to a certain order relation, needs a generic parameter representing the list elements’ type. That type is not arbitrary: it must support an order relation. To state that any actual generic parameter must be a descendant of the library class COMP ARABLE, describing objects equipped with an order relation, use constrained genericity to declare the class as SORTABLE_LIST [G –> COMPARABLE].
The genericity mechanism should support the constrained form of genericity.
It should be possible to redefine the specification, signature and implementation of an inherited feature. Redefinition may affect the implementation of a feature, its signature (type of arguments and result), and its specification.
In programming languages and type theory, polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types. It should be possible to attach entities (names in the software texts representing run-time objects) to run-time objects of various possible types, under the control of the inheritance-based type system.
Calling a feature on an entity should always trigger the feature corresponding to the type of the attached run-time object, which is not necessarily the same in different executions of the call. Dynamic binding has a major influence on the structure of object-oriented applications, as it enables developers to write simple calls (meaning, for example, “call feature turn on entity my_boat”) to denote what is actually several possible calls depending on the corresponding run-time situations. This avoids the need for many of the repeated tests (“Is this a merchant ship? Is this a sports boat?”) which plague software written with more conventional approaches.
Object-oriented software developers soon develop a healthy hatred for any style of computation based on explicit choices between various types for an object. Polymorphism and dynamic binding provide a much preferable alternative.
Run-time Type Interrogation
In some cases, an object comes from the outside, so that the software author has no way to predict its type with certainty. This occurs in particular if the object is retrieved from external storage, received from a network transmission or passed by some other system. It should be possible to determine at run time whether the type of an object conforms to a statically given type.
Deferred Features and Classes [Note — Is this equivalent to the concept of Protocols?]
In some cases for which dynamic binding provides an elegant solution, obviating the need for explicit tests, there is no initial version of a feature to be redefined. For example class BOAT may be too general to provide a default implementation of turn. Yet we want to be able to call feature turn to an entity declared of type BOAT if we have ensured that at run time it will actually be attached to objects of such fully defined types as MERCHANT-SHIP and SPORTS_BOAT.
In such cases BOAT may be declared as a deferred class (one which is not fully implemented), and with a deferred feature turn. Deferred features and classes may still possess assertions describing their abstract properties, but their implementation is postponed to descendant classes. A non-deferred class is said to be effective. It should be possible to write a class or a feature as deferred, that is to say specified but not fully implemented.
Memory Management and Garbage Collection
The language should make safe automatic memory management possible, and the implementation should provide an automatic memory manager taking care of garbage collection.
Implementation and Environment
Software development is an incremental process. Developers do not commonly write thousands of lines at a time; they proceed by addition and modification, starting most of the time from a system that is already of substantial size.
For example, if you change a feature f of class C, you must be certain that every descendant of C which does not redefine f will be updated to have the new version of f, and that every call to f in a client of C or of a descendant of C will trigger the new version. System updating after a change should be automatic, the analysis of inter- class dependencies being performed by tools, not manually by developers.
The time to process a set of changes to a system, enabling execution of the updated version, should be a function of the size of the changed components, independent of the size of the system as a whole. In the case of compiled languages, this requires the compiler to be incremental.
An object will often contain references to other objects; since the same may be true of these objects, this means that every object may have a large number of dependent objects, with a possibly complex dependency graph (which may involve cycles). It would usually make no sense to store or retrieve the object without all its direct and indirect dependents. A persistence mechanism which can automatically store an object’s dependents along with the object is said to support persistence closure.
A persistent storage mechanism supporting persistence closure should be available to store an object and all its dependents into external devices, and to retrieve them in the same or another session.
For some applications, mere persistence support is not sufficient; such applications will need full database support. The notion of object-oriented database is covered in a later chapter in Bertrand Meyer’s book, which also explores other persistent issues such as schema evolution, the ability to retrieve objects safely even if the corresponding classes have changed.
Automatic tools should be available to produce documentation about classes and systems. Assertions, as already noted, help make such software-extracted documents precise and informative.
Interactive browsing facilities should enable software developers to follow up quickly and conveniently the dependencies between classes and features.
An object-oriented environment should provide good libraries, and mechanisms to write more.
An object-oriented development environment must provide reusable classes addressing these common needs of software systems. Reusable classes should be available to cover the most frequently needed data structures and algorithms.
Graphical User Interfaces
Reusable classes should be available for developing applications which provide their users with pleasant graphical user interface.
Library Evolution Mechanisms
Mechanisms should be available to facilitate library evolution with minimal disruption of client software.
Library Indexing Mechanisms
Another problem raised by libraries is the need for mechanisms to identify the classes addressing a certain need. This criterion affects all three categories: libraries, language (as there must be a way to enter indexing information within the text of each class) and tools (to process queries for classes satisfying certain conditions). Library classes should be equipped with indexing information allowing property-based retrieval.