One can think of information hiding as being the principle and encapsulation being the technique. A software module hides information by encapsulating the information into a module or other construct which presents an interface.



For every complex problem, there is a solution that is simple, neat, and wrong.



—H. L. Mencken

Verbing Weirds Language







Object Design: Roles, Responsibilities, and Collaborations focuses on the practice of designing objects as integral members of a community where each object has specific roles and responsibilities. The authors present the latest practices and techniques of Responsibility-Driven Design and show how you can apply them as you develop modern object-based applications.





1 + 2

+ 2

1

1

+ 2

1.0

1.0 + 2

2 + 1.0

Integer

Float

CurrencytwoPlaceDecimal

1 + ThirtyCents

ThirtyCents + 1

==

equal?

eql?

eqv?

x.eql?(y)

y.eql?(x)

x

y

list

('foo' 'bar' 'blitz')

{ 0 => 'foo', 1 => 'bar', 2 => 'blitz' }

Actually I made up the term object-oriented and I can tell you I did not have C++ in mind. The important thing here is that I have many of the same feelings about Smalltalk.



—Alan Kay

has_one

belongs_to

<=>

+

<=>

equivalent?

There are two real approaches to object-orientation. The first is known as message-passing. You send an object a message and ask it to deal with it. (This would not work with many people in this newsgroup.) The meaning of the message is local to the object, which inherits it from the class of which it is an instance, which may inherit it from superclasses of that class…



The second approach is generic functions. A generic function has one definition of its semantics, its argument list, and is only specialized on particular types of arguments.



compareTo(...)

equivalent?

Equivalent

eval

Eqivalent.eval(foo, bar)

eval



public static boolean eval (List foo, List bar) { ... }

public static boolean eval (List foo, Map bar) { ... }

public static boolean eval (Map foo, List bar) { return eval(bar, foo); }

public static boolean eval (Map foo, Map bar) { ... }

==



public static boolean eval (Object foo, Object bar) { return foo == bar; }

List

Map

eval

eval

eval (Object foo, Object bar)

noun.verb(...)

verb



public static void printSomething(Object foo) {

System.out.println(foo.toString());

}

toString

foo

Object

toString



public static void printSomethingElse (Object foo, Object bar) {

if (Equivalent.eval(foo, bar))

System.out.println("2 x " + foo);

else System.out.println(foo.toString() + ", " + bar.toString());

}

eval (Object foo, Object bar)

eval (List foo, List bar)

eval

foo

bar

Equivalent.eval

Visitable

equivalent?



interface Visitable {

Object accept(final Visitor visitor);

}



interface Visitor {

Object visit(final Object obj);

Object visit(final List list);

Object visit(final Map map);

}



public class Equivalent {



static boolean list_list (List foo, List bar) { ... }

static boolean list_map (List foo, Map bar) { ... }

static boolean map_map (Map foo, Map bar) { ... }

static boolean object_object (Object foo, Object bar) { ... }



public static boolean eval (final Visitable foo, final Visitable bar) {

return foo.accept(

bar.accept(

new Visitor () {

public Object visit(final Object bar) {

return new Visitor () {

public Object visit(final Object foo) {

return object_object(foo, bar);

}

public Object visit(final List foo) {

return object_object(foo, bar);

}

public Object visit(final Map foo) {

return object_object(foo, bar);

}

}

}

public Object visit(final List bar) {

return new Visitor () {

public Object visit(final Object foo) {

return object_object(foo, bar);

}

public Object visit(final List foo) {

return list_list(foo, bar);

}

public Object visit(final Map foo) {

return list_map(bar, foo);

}

}

}

public Object visit(final Map bar) {

return new Visitor () {

public Object visit(final Object foo) {

return object_object(foo, bar);

}

public Object visit(final List foo) {

return list_map(foo, bar);

}

public Object visit(final Map foo) {

return map_map(foo, bar);

}

}

}

}

)

)

}

}

Your entities or objects no longer need to know all about other types of entities;



It’s easier to make sure that commutation and symmetry are preserved when the code for a relationship is in its own class and not smeared over multiple entities.

interface Classifiable

{

int classify();

}



// I know this isn't valid Java, but it makes the example much clearer. The alternative is to tiresomely spell out every combination.

#define PAIR(a,b) (a|(b<<4))



abstract class DoubleDispatchable

{

abstract Object list_list(List a, List b);

abstract Object list_map(List a, Map b);

abstract Object map_map(Map a, Map b);

abstract Object object_object(Object a, Object b);



const int OBJECT = 0;

const int LIST = 1;

const int MAP = 2;



Object dispatch(Classifiable a, Classifiable b)

{

switch(PAIR(a.classify(), b.classify))

{

case PAIR(LIST, MAP): return list_map(a,b);

case PAIR(MAP, LIST): return list_map(b,a);

case PAIR(LIST, LIST): return list_list(a,b);

case PAIR(MAP, MAP): return map_map(a,b);

default: return object_object(a,b);

}

}

}



class Equivalent extends DoubleDispatchable

{

Object list_list(List a, List b) {...}

Object list_map(List a, Map b) {...}

Object map_map(Map a, Map b) {...}

Object object_object(Object a, Object b) {...}



bool eval(Classifiable a, Classifiable b) { return dispatch(a,b) != false; }

}



OOP is several different ideas put together, the most important of which is Fine-Grained Information Hiding.The basic principle of all OO languages is that relatively small things—such as individual accounts in a business program—each encapsulate both their data (in the form of members) and their algorithms (in the form of methods). Our notions of members and polymorphism both work to this goal of hiding information. There’s a lot more to most OO languages, such as whether they include a notion of types and what mechanisms they use for sharing common behaviour. But let’s look at this one principle: objects are responsible for their data and for their algorithms.There’s a general idea that in a well-constructed program, each object “knows” how it ought to behave. That’s what its methods are for. Quite obviously, objects cannot be responsible for everything involving them in a program. If each object completely encapsulated all of the things it could do or be involved in, you would never pass one object as a parameter in a message to another object.For example, you would never have collections. If every object “knew” how to organize itself into collections, you wouldn’t need an Array or Hash, would you? In practice, each object in a system can be involved in many different actions. It has to be responsible for some of them, and it has to play a secondary, passive role in others. Most OO programs do not have every object implement its own collections methods. They may include some form of specialization so you can have an array of accounts, but an array of accounts is still not an account.In the English language, we have the idea of a Subject and an Object in a sentence. For example, when we say “Jack loves Jill,” Jack is a subject and Jill is an object. Jack loves. Jill is loved. It’s the same in OO programs. Sometimes objects are actively doing things through their methods. Sometimes other object’s methods are doing things with them.Good OO design is, in part, doing a good job of choosing the right bifurcations: given a list of nouns and verbs, making the right decisions about which nouns ought to be the active nouns, the subjects, the ones that “own” the verb in the form of a method. And thus consciously making decisions about which objects ought to be the passive nouns, the objects of the verbs, the ones that don’t implement the methods.Unfortunately, there are lots of places where we can err on the side of giving too much responsibility to individual objects. It’s understandable, given that OO is theoretically all about objects being responsible for themselves. But as in many other things, in practice good OO is about objects being responsible for a little as possible (but no less!), not as much as possible.One common symptom of this problem is a system that has objects for all of the obvious nouns or entities, but not for the verbs . OO began with languages like Simula , where the paradigm was trying to represent real-world entities such as automobiles on a highway. From that time forward, the emphasis has been on having objects for each noun in the problem domain. In such traditionally-organized OO programs, the “verbs” or actions are all attached to objects as methods.Not all “verbs” have a clear separation between a single entity that is the subject or active entity that ought to own the verb’s definition and the secondary, passive subject entities that should not own the verb’s definition. The easiest examples of this are operations that are intended to be commutative For example, many languages define addition as a method belonging to numbers or magnitudes. In Smalltalk, the expressionactually means “send the messageto the object.” At first glance, this seems elegant: the numberhandles the messageas integer addition, whilewould handle the same message with floating point arithmetic. What more could you want?Well, there is a huge problem with this arrangement: Addition is commutative.must give the same result as. Using a simple message to implement addition means that you must be excruciatingly careful to handle all of the possible cases so that you do not accidentally violate this property. Now of course, the designers of system classes likeandwent to this trouble. But if you want to add another magnitude class—say—you have to open up all of the system classes and modify them so thatgives the same result asOf course, you may not need to implement a new magnitude class. Fine. But what about symmetric relations like comparison? This is a major pitfall for OO developers: in many cases you need to write a test of equivalence or equality (operations likeand all of the other variations on the same theme). In every one of these cases, horrible things will happen if your operation is not symmetric. For every case,if-and-only-ifThis is obviously easy whenandare both the same kind of object. What happens when they’re different, but still logically equivalent? It turns out that implementing commutative operations and symmetric relations as methods doesn’t work very well. It forces you to smear duplicate logic over many different classes (or prototypes, if your language swings that way).Here’s a practical example. Let’s say you want to implement a form of equivalence for collections. For ordered collections like lists, what you want is that if two ordered collections have the same members, in the same order, they are equivalent. It’s easy to imagine writing such a method as a mixin for all of your ordered collections. It obviously knows about iterating over ordered collections (recursively, if you grew up with Godel, Escher, Bach on your night stand). Note that you may not have an indexed collection: you might have awhere you simply retrieve values in order.And likewise, you can write a collection equivalence method for dictionaries like hash tables: if two objects have the same values at the same keys, they are equivalent. Again, a simple mixin will handle things for dictionaries.Now comes the wrinkle: you decide that an ordered collection ought to be equivalent to a dictionary where the keys are the integers ascending from zero. In other words,ought to be equivalent to. How are you going to code this? Well, the dictionary mixin could obviously handle equivalence to an ordered list. But we need symmetry, so we have to “open up” the ordered collection mixin and add code for equivalence to dictionaries.I’m holding my nose, we have not one but two different code smells: 1) Why is one piece of logic in two different places? 2) Why do ordered collections know anything at all about dictionaries, and why do dictionaries know anything at all about ordered collections? The latter is especially disturbing: the whole point of OO is information hiding. How does having ordered collections and dictionaries knowing about each other help us to hide information?The obvious answer to me is that the knowledge of how to compare an ordered collection to a dictionary does not belong in ordered collections or in dictionaries. The requirement that relations like equivalence be symmetrical across heterogeneous types implies that the types themselves cannot be responsible for implementing equivalence for themselves.There are similar problems of code duplication and information leakage apply to modelling relations (why do we declareandin Rails) and implementing theoperator in Ruby. It looks like having verbs “belong to” the subject noun is often a good idea, but not always a good idea.Maybe some verbs belong to objects, but some are best on their own? Maybeandandreally ought to be emancipated from their subservience to objects and ought to have their own definitions.What we ought to do is take some of the verbs and give them their own place in our programs, instead of hanging them off nouns. This isn’t such a revolutionary idea: Common Lisp’s Metaobject Protocol does this exact thing, providing generic functions . A generic function is, in effect, a verb raised to the same level of abstraction as a noun.This isn’t some revolutionary idea limited to “powerful” languages either: the Java collections framework uses a Comparable interface for ordering collections. Themethod belongs to an object. By way of—ahem—comparison, the Comparator interface extracts comparison out of the subject object and puts it in a separate function object. You can perform sorts in Java either way.If we aren’t using Common Lisp, can we build the verbs we want out of the tools at our disposal? In other words, can we Greenspun generic functions in languages like Java and Ruby?Let’s start by thinking about generic functions in a Java-like language.Returning to our example of writing, we might make anclass with a single method, perhaps we can call it. So we end up with something like. Java-like languages allow us to write different versions of themethod with different type signatures, so we can write:And so forth. Are we done?No, our code is broken. What happens when we decide that the “default” equivalence is therelationship. We can’t write:This is hideously broken in languages like Java. You’re almost all nodding in agreement, but please be patient while I explain it anyway: you probably want to pass this along to someone who really needs to be told why it is broken, so why don’t I go ahead and explain it for them?What you want is that if two objects are of the more specific types—and—we will call the more specific version of themethods. But if we can’t “match” one of the more specificmethods, we want to use. Too bad, that’s not how Java works. Java uses two completely different ways to figure out which method to call when you overload methods!Way number one is is for figuring out that when you call, where do we find the definition for? This lookup is effectively done at run time, so that even if your code looks like this:Java will look up the methodbased on’s actual type when the method is called, even though you declared it to be an. That’s polymorphism at work, and it’s the information hiding working for us. Each object can do it’s own thing whereis concerned, and we don’t have to worry about it. This is called single dispatch , because it figures out which method to call based on just one of the nouns, the subject noun a/k/a the receiver of the method invocation.But that’s not what happens when we write this:It will always call. It willcallif you pass it two lists. That’s because although each of our methods have the same name——Java treats them as different methods, and it figures out which one to call based on the declared types of the parameters at compile time, not on the actual types of the parameters’ values at run time.Besides writing a Lisp interpreter in Java, your next best bet for building a generic function the way we want it is to find a way to turn Java’s single dispatch into a multi-dispatch, to dispatch on two nouns,andThe good news is this: dispatching at run time on two different types is a well-known problem, and the solution is called double dispatch . The problem with double dispatch is that it moves our equivalence code back into our nouns, and we don’t want that.The Visitor pattern might be handy: it’s a way to add methods to an object at run time in a language like Java that supposedly doesn’t do that. If we decide that everything to be compared usingimplements an interface called, we can build a double dispatch system that doesn’t require putting anmethod in the entities being compared:If that looks like a lot of work to you, I agree. You’re basically replicating Java’s run time dispatching on two types, so you need a bit of a matrix. Is it worth the effort? Let’s consider what this wins you:And best of all, you have a nice place for your verbs, and they are no longer second-class citizens behind the nouns.Update: A few people have suggested alternate approaches to implementing multiple dispatch in Java. I think there are various trade-offs to be made, and several different implementations ought to be considered before you write production code.However, the point of the article is to suggest that not all functions should be implemented as methods of subject objects. I think it makes that point regardless of what you think of using a Visitor and a double dispatch.Here’s an alternate approach from Laurie Cheers What trade-offs, you ask? The Visitor pattern given gets the compiler to guarantee that you write each of the nine cases, whereas hand-written tests and logic simplifies the code.I specifically chose the Visitor pattern because it seemed more in keeping with the spirit of the Java language and culture, trading verbosity for compiler safety.I'm extremely comfortable with the other trade-off, emphasizing readability and simplicity. Although, if you go far enough down that road, you might as well look at other languages ;-)

Labels: java, lispy, popular