topical media & game development

talk show tell print

object-oriented programming

Prototypes -- delegation versus inheritance

subsections:


The classical object model (which is constituted by classes, objects and inheritance) not only has its theoretical weaknesses (as outlined in the previous section) but has also been criticized from a more pragmatic perspective because of its inflexibility when it comes to developing systems. Code sharing has been mentioned as one of the advantages of inheritance (as it allows incremental development). However, alternative (read more flexible) forms of sharing have been proposed, employing prototypes and delegation instead of inheritance.

Alternative forms of sharing

A class provides a generic description of one or more objects, its instances. From a formal point of view, classes are related to types, and hence a class may be said to correspond to the set of instances that may be generated from it. This viewpoint leads to some anomalies, as in the case of abstract classes that at best correspond to partially defined sets. As another problem, in the context of inheritance, behavioral compatibility may be hard to arrive at, and hence the notion of subtype (which roughly corresponds with the subset relation) may be too restrictive. In practice, we may further encounter one-of-a-kind objects, for which it is simply cumbersome to construct an independent class. In a by now classical paper,  [
Lieber] proposes the use of prototypes instead of classes. The notion of prototypes (or exemplars) has been used in cognitive psychology to explain the incremental nature of concept learning. As  [Lieber] notes, the philosophical distinction between prototypes (which provide a representative example of an object) and classes (which characterize a set of similar objects) may have important pragmatical consequences as it concerns the incremental definition of (hierarchies of) related objects. First, it is (claims Lieberman, 1986) more natural to start from a concrete example than to start from an abstract characterization as given by a class. And secondly, sharing information between prototypes and clones (that is, modified copies) thereof is far more flexible than the rather static means of sharing code as supported by the class inheritance mechanism. Code sharing by inheritance may be characterized as creation time sharing, which in this respect is similar to creating a copy of the object by cloning. In addition, prototypes may also support lifetime resource sharing by means of delegation. In principle, delegation is nothing but the forwarding of a message. However, in contrast to the forwarding mechanism as described in sections delegation-in-Java and hush-idioms, delegation in the context of prototypes does not change implicit self-reference to the forwarding object. In other words, when delegating a message to a parent object, the context of answering the message remains the same, as if the forwarding object answers the request directly. See slide 5-prototypes.

Prototypes -- exemplars


slide: Prototypes

An almost classical example used to illustrate prototypical programming is the example of a turtle object that delegates its request to move itself to a pen object (which has x and y coordinate attributes and a move method). The flexibility of delegation becomes apparent when we define a number of turtle objects by cloning the pen object and adding an y coordinate private to each turtle. In contrast to derivation by inheritance, the x coordinate of the pen object is shared dynamically. When changing the value of x in one of the turtle objects, all the turtle objects will be affected. Evidently, this allows considerable (and sometimes unwished for) flexibility. However, for applications (such as multimedia systems) such flexibility may be desirable.

Design issues

Strictly speaking, prototype-based delegation is not stronger than forwarding in languages supporting classes and inheritance. In  [Dony], a taxonomy of prototype-based languages is given. (This taxonomy has been partly implemented in Smalltalk. The implementation, however, employs so-called class-variables, which are not unproblematic themselves. See section meta.) One of the principal advantages of prototype-based languages is that they offer a consistent yet simple model of programming, consisting of objects, cloning and delegation. Yet, when designing a prototype-based language, a number of design decisions must be made (as reflected in the taxonomy given in Dony {\it et al.}, 1992). These issues concern the representation of the state of an object, how objects are created and the way in which delegation is handled. See slide 5-proto.

State

  • slots -- parents
  • variables and methods

Creation

  • shallow cloning
  • deep cloning

Delegation

  • implicit delegation
  • explicit delegation

slide: Prototypes -- state, creation, delegation

The basic prototype model only features slots which may store either a value or a piece of code that may be executed as a method. Alternatively, a distinction may be made between variables and methods. In both cases, late binding must be employed to access a value. In contrast, instance variable bindings in class-based languages are usually resolved statically. When creating a new object by cloning an existing object, we have the choice between deep copying and shallow copying. Only shallow copying, however, allows lifetime sharing (since deep copying results in a replica at creation time). Shallow copying is thus the obvious choice. Finally, delegation is usually handled implicitly, for instance by means of a special parent slot, indicating the ancestor of the object (which may be changed dynamically). Alternatively, it may be required to indicate delegation explicitly for each method. This gives a programmer more flexibility since it allows an object to have multiple ancestors, but at the price of an increase in notational complexity. Explicit delegation, by the way, most closely resembles the use of forwarding in class-based systems. One of the, as yet, unresolved problems of delegation-based computing is how to deal with what  [Dony] call split objects. An object may (internally) consist of a large number of (smaller) objects that are linked to each other by the delegation relation. It is not clear how to address such a complex object as a single entity. Also, the existence of a large number of small objects that communicate by message passing may impose severe performance penalties.

Implementation techniques -- Self

A major concern of software developers is (often) the runtime efficiency of the system developed. An order of magnitude difference in execution speed may, indeed, mean the difference between acceptance and rejection.

Improving performance

  • special-purpose hardware
  • hybrid languages
  • static typing
  • dynamic compilation

slide: Improving performance

There are a number of ways in which to improve on the runtime efficiency of programs, including object-oriented programs. For example,  [Ungar92] mention the reliance on special-purpose hardware (which thus far has been rapidly overtaken by new general-purpose processor technology), the use of hybrid languages (which are considered error-prone), static typing (which for object-oriented programming provides only a partial solution) and dynamic compilation (which has been successfully applied for Self). See slide 5-opt. As for the use of hybrid languages, of which C++ is an example, the apparent impurity of such an approach may (to my mind) even be beneficial in some cases. However, the programmer is required to deal more explicitly with the implementation features of the language than may be desirable. In general, both with respect to reliability and efficiency, statically typed languages have a distinct advantage over dynamically typed (interpreted) languages. Yet, for the purpose of fast prototyping, interpreted languages (like Smalltalk) offer an advantage in terms of development time and flexibility. Moreover, the use of (polymorphic) virtual functions and dynamic binding necessitate additional dynamic runtime support (that is not needed in strictly procedural languages). Clever compilation reduces the overhead (even in the case of multiple inheritance) to one or two additional indirections.

Dynamic compilation

The language Self is quite pure and simple in design. It supports objects with slots (that may contain both values and code, representing methods), shallow cloning, and implicit delegation (via a designated parent slot). Moreover, the developers of Self have introduced a number of techniques to improve the efficiency of prototype-based computing.

Self -- prototypes

  • - objects, cloning, delegation

Dynamic compilation -- type information

  • customized compilation
  • message inlining
  • lazy compilation
  • message splitting

slide: Dynamic compilation -- Self

The optimization techniques are based on dynamic compilation, a technique that resembles the partial evaluation techniques employed in functional and logic programming. Dynamic compilation employs the type information gathered during the computation to improve the efficiency of message passing.

Whenever a method is repeatedly invoked, the address of the recipient object may be backpatched in the caller. In some cases, even the result may be inlined to replace the request. Both techniques make it appear that message passing takes place, but at a much lower price. More complicated techniques, involving lazy compilation (by delaying the compilation of infrequently visited code) and message splitting (involving a dataflow analysis and the reduction of redundancies) may be applied to achieve more optimal results.

Benchmark tests have indicated a significant improvement in execution speed (up to 60% of optimized C code) for cases where type information could be dynamically obtained. The reader is referred to  [Ungar92] for further details.



(C) Æliens 04/09/2009

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.