Today's Question:  What does your personal desk look like?        GIVE A SHOUT

Prototypes and Object Orientation

  David Chisnall        2011-09-02 11:51:26       2,480        0    

David Chisnall takes a look at the two dominant paradigms in object-oriented languages (classes and prototypes) and discusses the strengths and weaknesses of each.

Two terms are quite often confused when describing programming languages:class-based and object-oriented:

  • Simula was the first class-based language. It provided classes (actually implemented using closures) as a means of encapsulating abstract data types.
  • Smalltalk was the first object-oriented language. It provided a mechanism for decomposing programs into subprograms that ran on a simplified model of a computer and communicated with each other via message-passing.

The confusion comes from the fact that Smalltalk was also a class-based language. Most of the members of the Smalltalk family, such as Objective-C and Java, were also class-based. Some, however, were not. The first example of a language that was object-oriented, but not class-based, is Self; the most famous example is JavaScript. These languages use the prototype-based model. In this article, we'll take a look at how the two models differ, and how either can be represented in the other.

Classes and Delegates

A very common pattern in object-oriented languages is the idea of delegation. An object uses another to provide some of its functionality. This pattern is especially common in user interfaces, as a mechanism for combining the generic behavior of a view with some specific behavior for the program.

In class-based languages, subclassing is a special case of delegation. Each class has some number of superclasses to which it delegates. When you request some functionality from a class, it either implements that functionality itself, or it delegates the functionality t to a superclass. This design is extended further by objects, which are instances of classes, and therefore delegate most of their operations to a class. In a language like Smalltalk or Java, instances just contain data—they use their class delegate to provide all method implementations.

This level of delegation creates the only really confusing thing in the Smalltalk system: themetaclass. In Smalltalk, everything is an object, including classes. Objects are instances of classes; therefore, classes are instances of metaclasses. For consistency, the metaclasses must be instances of metametaclasses, which should be instances of metametametaclasses, and so on. To avoid this infinite recursion, metaclasses are actually also instances of classes. This same model exists in Objective-C, by the way, but is only exposed when you interact with the runtime library directly.

The existence of both kinds of delegation makes the delegation chain quite complex. Every message is delegated to the object's class and then delegated up the superclass chain. Messages are never delegated along the metaclass chain, except for the initial delegation.

In a prototype-based system, every object has its own methods, and delegates to its prototype. This is very simple—you have only one kind of delegation chain, not one and a half. In JavaScript, every object has either zero prototypes or one prototype. In Self, an object can have an arbitrary number of parents, allowing the equivalent of multiple inheritance.

Differential Inheritance

In a class-based language, everything about an object is defined by its class, which defines the state that the object stores, as well as its possible interactions. You typically can't add instance variables (state) to an object in a class-based language if they're not declared in the class (or a superclass).

A prototype-based language removes this special treatment of classes. Any object can be extended in exactly the same way as a class, adding new properties as well as new methods.

The thing that often trips up people moving from class-based languages to prototype-based languages is the lack of distinction between methods and instance variables in a typical prototype-based language. When you send a message to a Smalltalk object, the object's class is used to look up the method. When you access an instance variable, it's loaded from some fixed offset from the start of the object (or, in some cases, from a linked list at the end).

In Self-like languages, both of these possibilities are handled by the slot mechanism. Objects have a collection of slots, indexed by string values. Rather than storing state at some fixed offset from the object's start, the state is stored in this dictionary. This approach is necessary because Self-style objects don't have a fixed layout determined by their class.

The same mechanism is used for method lookups. In fact, Self and JavaScript don't have methods as a language concept—they have closures stored in slots. When you call a method, you're really just invoking a closure that has been stored in a slot on an object.

Because (the equivalent of) both methods and instance variables are stored using the slot mechanism, they're subject to the same inheritance pattern. This pattern is called differential inheritance—only the differences between an object and its prototype are stored.

This design lets you do some very interesting things, but it can also cause some confusion. It's important to remember that a clone is not the same as a copy—slot lookup uses the same delegation/inheritance mechanism as method lookup in a class-based language. For example, if you create an object with a count field and then clone it, inspecting the count field in either the original object or the clone will give you the same slot. If you modify the value in the original, the change is visible in both. If you modify the clone, the new value is visible only in the clone. This is because lookup happens in the same way that method lookup happens with classes: If you override a method in a subclass, that method is visible only in the subclass. If you replace the method in the superclass, that change is visible in all subclasses that don't override the method.

Hidden Class Transforms

One of the downsides of the prototype-based approach is that accessing object properties is very expensive. Accessing an instance variable in a Smalltalk object is just a matter of (the compiler) adding a constant offset to the object pointer to get the address. The same operation in Self requires a dictionary lookup.

However, often there are a lot of objects with a very similar set of properties. In this case, a clever compiler can implement a class-based model. It transforms a group of objects into instances of a (hidden) class, which describes the fixed layout. Accessing the fixed set of fields is very cheap, while other properties are accessed via the same slot mechanism.

This structure also allows some other optimizations. For example, you can cache method lookups more easily if the lookup result is shared among a few objects. The Self team had a very fast implementation in the 1990s—faster than any Smalltalk implementation of the time, and around 50% of the speed of an equivalent C++ implementation. (Although C++ compilers in those days were pretty bad, so that wasn't as much of a boast as it would be now.)

In principle, you can implement any prototype-based program in a class-based language by having a single class for each object. Indeed, that's one of the simplest ways of implementing the hidden class transform. You begin by implicitly creating a class for every object in the system, inheriting from the object's prototype's class. If the new class doesn't declare any new methods or instance variables, you remove it. Eventually, you're left with a relatively large number of classes that have a lot of instances, and a few that have only one or two.

The hidden class transform only works (and provides a benefit) because people can implement something equivalent to classes in prototype-based languages. This is very common in JavaScript. In fact, several frameworks advertise adding a class-based model to the language as a feature. Even the core JavaScript library has a few object types—and the DOM has a lot more—that are typically used as if they were classes, with instances modifying only existing properties, rather than defining new properties or new methods.

Models and Views

Back when Apple still did innovation, its engineers produced a handheld computer called the Newton. The team that worked on this project was particularly interested in object-oriented design patterns, publishing a very interesting paper that described the various approaches that class and prototypes encourage.

The team's conclusion was that class-based languages are a good fit for writing model objects, which typically have a large number of instances that differ only by state, not by behavior. For implementing views, they proposed that prototype-based languages were better.

Most of the time, one view has subtly different behavior from another. In a framework written for a language like Smalltalk or Objective-C, this difference is typically abstracted into a delegate—a separate object that controls the behavior. In some toolkits, users are encouraged to subclass the view classes to add behavior. Doing so is quite irritating in most languages, because creating new classes typically requires some boilerplate code and overhead, which can be a waste of effort for a fairly small change.

It's instructive to think of design patterns as hacks that work around flaws in the language. This is a view commonly taken by Lisp proponents, who point out that almost everything in the Gang of Four's famous book can be implemented as a reusable Lisp macro. Whenever you apply a design pattern, ask yourself what feature could be added to the language to make the pattern obsolete. In the case of the delegation pattern, often the answer is prototypes.

Of course, in a purely prototype-based language, you'll often find yourself applying design patterns that effectively amount to reinventing classes.

Source ï¼› http://www.informit.com/articles/article.aspx?p=1739161&seqNum=1

PROTOTYPE  COMPARISON  OOP  DIFFERENTIAL  MO 

Share on Facebook  Share on Twitter  Share on Weibo  Share on Reddit 

  RELATED


  0 COMMENT


No comment for this article.