Monday, August 25, 2008

8 perceptual metaphors for OOP objects

Why Learning F# Is So Difficult at got net? (I compliment the blog's subtitle "Kevin Hazzard's Brain Spigot") has an insightful explanation for why functional programming code is so confusing at first: usually, programmers don't have the necessary perceptual primitives, the prototypes, that would enable rapid recognition/comprehension of the code. They're expecting mutable variables, not immutable lists. They're expecting "foreach", not "map". They're not expecting statements, expressions, and functions to be somewhat synonymous. They're not expecting functions to be inputs or outputs of functions, nor are they expecting partial function evaluation. They're not expecting recursion to play a large role, perhaps because they assume it will blow the stack (which is a good opportunity to explain tail-recursion optimization). And, naturally, they're not expecting the operators to look and act so differently.

That said, the need to learn new perceptual prototypes as part of adjusting to a different programming paradigm applies to OOP, too. My experience of switching to OOP had its own moments of bewilderment, such as "Huh? Invoking a subroutine on this data type runs code two levels up in an 'inheritance hierarchy'?". I know of no less than 8 ways or metaphors for perceiving the most fundamental concept of OOP, objects:
  • Supervariables. The supervariable metaphor is most applicable to small classes that don't do much more than provide a handful of convenience methods for representing and modifying abstract data types. "Supervariable" emphasizes that an object not only stores a small conglomeration of data but also provides prefabricated code for common tasks on that data. An alternative name for this metaphor is "smart variable". A ComplexNumber object that has a produceConjugate method fits the supervariable metaphor.
  • Miniature programs. The miniature program metaphor is most applicable to classes that, like a control panel, expose intricate, full-featured capabilities. "Engine" in the class name could be a clue. (In practice, objects that are closest to "miniature programs" probably employ I/O and various other objects internally.) The point is that creating an object and running one or more of its methods can be similar to starting a program and directing it to carry out actions. Of course, the metaphor is explicit when a program specifically furnishes an "API object" interface in addition to GUI or command-line.
  • Nouns. The noun metaphor is the most straightforward and the most taught. The object represents a noun and simply consists of the noun's attributes and activities. It's a Car, a Square, a Dog (that inherits from Vehicle, Shape, Animal, respectively). It's an abstract simulation of its real counterpart in the problem domain. Since in nontrivial problems 1) the problem domain overflows with too many irrelevant nouns and 2) not all necessary parts of a program have real-world analogues in the problem domain, the noun metaphor isn't seamless nor self-sufficient. It's a good starting point for analysis and design, however.
  • Memo senders. One of the tricky mental shifts for OOP beginners is decentralization of control flow, especially when it's event-driven. Each object has its own limited, distinct responsibility and role, so the way to accomplish a larger purpose is collaboration and communication among objects. No object is inherently primary, though execution must start at one particular method. In OOP, the metaphor of object interaction isn't a top administrator dictating orders or stage actors enacting a script; it's a cooperative group of employees who send memos to one another in order to complete individual assignments. And "employees" shouldn't need to "send memos" to every other employee in the "building" - see Law of Demeter.
  • Lieutenants. From the standpoint of the memo-sending metaphor, the "lieutenant" metaphor is about the contents of the "memos". A lieutenant (or vice-president) isn't told exactly what to do (the "how") but the goal to be met (the "what"). The value of the lieutenant is in delegation. Objects shouldn't need to know much about other objects' work, as long as the objects do their "jobs". There's a good reason for this advice: the more an object knows and therefore assumes about other objects, the harder it is to modify and/or reuse each object independently. Effective error handling often requires a delicate compromise of an object knowing just enough about possible errors to respond the way it should - no more, no less (should the object abort, retry, ignore, fail?).
  • Instantiable scopes. Unlike the noun metaphor, the "instantiable scopes" metaphor is not likely to be included in introductory texts. It's one of the more obvious metaphors to seasoned programmers but one of the least obvious to novices. This is a rather literal and stark "metaphor", which is why it's of more interest to compiler and interpreter writers than to analysts. An object is a "bundle" of functions and variables whose implementation involves "this" (or "self") pointers and vtables. Sometimes, people involuntarily learn this metaphor when a serious problem occurs. Developers who are trying to add tricksy feature extensions to a language may need to think of objects in this way.
  • Data guards. Substitute "bouncers" for "guards" if desired. In this metaphor, objects are protective intermediaries between code and data. The object somehow mitigates the downsides of direct data access: checking that indexes are in-bounds, tying the disposal of acquired resources to garbage collection, etc. Like the lieutenant metaphor, a data guard object can separate an accessing object from dangerous knowledge, because the accessing object knows only as much about the data as the guard allows. This means, for instance, that the guard could change to obtain the data from a different source, or the accessing object could be reused in several different contexts at once as long as its data guard functions consistently. The obvious downside is that superfluous guards and multiple layers of guards complicate and slow the program.
  • Sets of behaviors. This is the most abstract metaphor of all. Many OO design patterns use it. According to this metaphor, objects represent actions instead of, well, objects. The object's focus is the execution of a specific algorithm, and not the expression of data. Therefore, the typical name of a behavioral object refers to the role it serves in the program: bridge, adapter, factory, iterator, strategy. Viewed through this metaphor, the important difference between all objects is behavior. Objects that have the same behavior shouldn't require multiple classes. The behavior of subclasses shouldn't violate the behavior expectations of the superclass. An object is what an object does.

No comments:

Post a Comment