Scholar and hymnwriter Caleb T. Winchester described a classic as “something that everybody wants to have read and nobody wants to read.” For me, the Design Patterns book, by Erich Gamma and the rest of the “Gang of Four,” is a prime example. As an object-oriented software developer, unless you want to repeat the mistakes of the uninformed, you want to have read this book. There’s a reason it’s in its 40th printing! But it’s a daunting volume, full of abstract ideas.
Chapter 1 Terminology
Chapter 1 does not make it easy. The terminology is abstract, especially because it’s not tied to any one OO programming language. Where examples of languages are given, the authors don’t mention Java (the book was published in 1995, the same year that Java 1.0 went public). Terms like interface and class are familiar from Java programming, yet they don’t always mean the same thing in the book as in Java. The main reason for this post is to write down some key things I’m learning about Design Patterns (DP) terminology.
interface: If there is any key word to the book, this is probably it. DP is famous for the principle “Program to an interface, not an implementation.” The book defines interface as follows: “The set of all signatures defined by an object’s operations is called the interface to the object.” OK, this gives a rough idea, but it gets more complicated than that.
In Java, there can be Interfaces (using the capital letter to distinguish Java usage from DP usage) that never represent “the set of all signatures defined by [any particular] object’s operations.” For example, an Interface might not (yet) be implemented by any object; or, maybe all the implementing objects also implement additional Interfaces. In DP, we soon read that an object may have many types, where a type is a name used to denote a particular interface. And that “Part of an object’s interface may be characterized by one type, and other parts by other types.” The only way I can make sense of that is if an interface can refer not only to the set of all signatures of an object’s operations, but also to any specified subset. So a more general definition for interface (in DP’s usage) might be “A set of [some, not necessarily all] signatures of operations that might be implemented by certain objects.” However, the interface of an object does seem to mean the set of all signatures of that object’s operations, even if that interface contains several other interfaces (“subsets” in DP). DP often leaves the distinction to context, so you have to keep your eye on the ball in order to understand which is meant.
The same is true of the word type. E.g. when DP says “[a] An object can have many types, and [b] objects of different classes can have the same type [italics added],” what does that mean? Does it mean that [a] The (entire) interface of an object can have many names, and [b] objects might be instances of classes that are all different implementations of the same interface and only that interface? Or does it mean that [a] An object can implement many different interfaces (each with its own name), and [b] objects might be instances of classes that share a common interface in addition to other possible operations?
Admittedly, in some cases it doesn’t matter. But sometimes it does. When we read,
When inheritance is used carefully … all classes derived from an abstract class will share its interface.
the sentences after this in the book make clear that the derived classes share the abstract class’s entire interface, but that interface is not necessarily the entirety of each derived class’s interface.
DP also doesn’t say explicitly whether every interface has a type (i.e. a name), or can have more than one (presumably it can). You could probably say, at least in Java, that every object has a type: the object is an instance of a class, which defines an interface, and you could use the name of the class as the name of the interface. Of course, not every Java class has an Interface: some classes will implement multiple Interfaces, and others, none. (But see below about the term type.)
There’s another nuance here: DP posits that “Objects are known only through their interfaces. There is no way to know anything about an object or to ask it to do anything without going through its interface” (emphasis mine). In Java, classes can have non-private variables, through which a program can get information from an object, or even, in some sense, ask it to do something. The foregoing principle implies, then, that non-private (instance) variables are part of an object’s interface, even though they are not operations. No doubt this is why people say that “all of your instance variables (a class’s nonconstant fields) should be private” — because the interface of an object should not expose implementation details. Doing so breaks encapsulation. (They also say that even accessor methods carry a similar risk… but that’s another topic all its own.) DP goes on to say “An object’s interface says nothing about its implementation…”, which confirms that Java non-private instance variables violate DP’s principles of interfaces.
On the other hand, the above statement “Objects are known only through their interfaces” clearly has exceptions. An obvious one is that an object may know about itself (e.g. about the contents of its variables) without going through its interface. A more subtle one is that an object inheriting from an ancestor often has privileged access to the ancestor’s non-public members (methods and variables). The latter issue comes up later as DP discusses inheritance vs. composition as means of defining one class’s implementation in terms of another’s. It’s pretty clear from this discussion that DP doesn’t consider privileged inheritance-based access to be part of the interface of the ancestor class.
operation: a procedure that an object defines; i.e. a method. Some sources draw the distinction that a method is an implementation of an operation, in a particular class. In analogy form, operation is to interface as method is to class. But DP doesn’t draw this distinction.
type: DP defines type specifically as “a name used to denote a particular interface.” However, later usage suggests that type is used interchangeably with interface, even when an interface’s name doesn’t seem to be in focus. For example,
Of course, there’s a close relationship between class and type. Because a class defines the operations an object can perform, it also defines the object’s type. [italics added]
The latter would be strange if type meant one of possibly many, or zero, names used to denote a particular interface. Continuing in the same paragraph,
When we say that an object is an instance of a class, we imply that the object supports the interface defined by the class.
Languages like C++ and Eiffel use classes to specify both an object’s type and its implementation. [italics added]
Again, the latter would be an odd thing to say if type means merely a name for an interface. It would make more sense if type here was a synonym for interface. One more example of that:
Any object can be replaced at run-time by another as long as it has the same type.
Clearly, what matters here is not the name of the objects’ interfaces, but the (relevant subsets of their) interfaces themselves. So from here on I’ll assume that type is often used synonymously with interface.
(object) composition: “assembling or composing objects to get more complex functionality.” One object obtains a reference to another and makes requests of it through its interface, rather than inheriting implementation via inheritance. Composition may take the form of aggregation or acquaintance.
aggregation: “implies that an object owns or has responsibility for another object. … Generally we speak of having or being part of another object. … Implies identical lifetimes.”
acquaintance: “implies than an object merely knows of another object. … called association or the using relationship.”
delegation: “a way of making composition as powerful for reuse as inheritance.” Object A receives a request, and forwards, or delegates, the request to object B. As such, this is “mere forwarding,” while “true delegation” implies that object A (the receiver of the request) passes a reference to itself to object B (the delegate). This way, the delegate has access to the interface of the receiver during processing of a request, as a parent classes’ inherited method would have access to the child caller object via the this pointer. I had to reread that last part a few times to get it right. In comparing delegation to inheritance, the receiver corresponds to the child, and the delegate, to the parent. Note that delegation is often used loosely to mean mere forwarding, in which the delegate has no access to the receiver.
One more caveat, on delegation terminology: The DP book uses receiving object and receiver to refer to the delegator, while some other sources, like Wikipedia, tend to use the same words to refer to the delegate. Of course it depends on which method call you’re thinking about receiving … the original request, or the delegation request. To avoid ambiguity, we could use delegator vs. delegate (some even call it delegatee).
“Program to an interface, not an implementation”
Don’t declare variables to be instances of particular concrete classes. Instead, commit only to an interface defined by an abstract class.
This famous “principle of reusable object-oriented design” shows up in section 1.6. The reasons for doing so make sense. But is it practical? Wouldn’t this mean that for every concrete class you define, you’d have to define another, abstract version of it (or an Interface) … except for the cases where you have multiple concrete classes extending the same abstract class?
I think it depends greatly on what you’re developing, and the level of reuse you expect. As is often the case, the specters of YAGNI and premature optimization loom over one’s shoulder as one contemplates how to achieve optimal code reusability. In creating Android Activities, you’re mostly using classes from an API that have been designed for you; the actual code you’re writing will not be reused much. But once you start creating other classes to represent real-world things to be used by multiple Activities, encapsulation and code reuse become more important. And if you’re writing a publicly available library, or even designing the Android app libraries themselves, it makes a lot more sense to spend time separating implementation from interface. DP talks about this late in chapter 1… the differing needs for reusability in applications vs. libraries vs. frameworks.
“Relating Run-Time and Compile-Time Structures”
Trying to understand one from the other is like trying to understand the dynamism of living ecosystems from the static taxonomy of plants and animals, and vice versa.
I liked that quote. I might have expected them to say “is like trying to understand the dynamism of a living organism from the physical structure of its body.” But I suppose taxonomy arguably has a special resemblance to static code structures like inheritance.
Anyway, on to chapter 2!