The Gang of Four got it wrong. Ruby's standard library has it wrong. Rails has it wrong.
Is it still wrong if everyone is doing it?
Yes.
The Gang of Four book Design Patterns gives us common vocabulary to understand basic patterns in Object-oriented programming. It helps all of us use the same terms when discussing our software and prevents confusion.
Sadly, this book seems to cause a whole hell of a lot of confusion.
They say "prefer composition over inheritance." That's great, there's good reason for that.
They say to use "delegation." That's great. Although there's not a single example of delegation in their book.
Delegation is a technique often cited as a way to create flexibility in your programs. That is to say, delegation is often given as the way to composition.
But delegation isn't what you think it is. The Gang of Four have lead you astray.
Worse still, almost every reference you'll find for "delegation" shows you examples of just object collaboration with message forwarding. They are examples of plain old method calls and not delegation.
Now imagine what your mentor would say about understanding basic programming concepts...
Don't have a mentor? That's ok. Imagine that you do. Now imagine what your mentor would say about understanding basic programming concepts...
You should probably understand them correctly, right?
So what is Delegation?
Delegation is easy to understand but we're so used to hearing and reading it in reference to something else. Let's fix that.
Henry Lieberman coined the term "delegation" in Using prototypical objects to implement shared behavior in object-oriented systems released with the OOPLSA '86 Conference proceedings on Object-oriented programming systems, languages and applications. That content is behind a pay wall, but you can read similar and updated content on his website
In there, you'll find exactly what delegation is, but I'm not sending you away to pore over another long article; I can simplify it for you. When you have time, however, go read Lieberman's own words.
Lieberman discussed this in terms of a GUI drawing tool. Here's the key idea behind delegation:
When a pen delegates a draw message to a prototypical pen, it is saying "I don't know how to handle the draw message. I'd like you to answer it for me if you can, but if you have any further questions, like what is the value of my x variable, or need anything done, you should come back to me and ask." If the message is delegated further, all questions about the values of variables or requests to reply to messages are all inferred to the object that delegated the message in the first place.
In short, when you send a message to an object, it has a notion of "self" where it can find attributes and other methods. When that object delegates to another, then any reference to "self" always refers to the original message recipient. Always.
The other inheritance
Class-based inheritance isn't the only game in town. Prototype-based inheritance is another way to structure your objects for behavior sharing. One approach sets behaviors in an abstract location (the class) and another sets it on instances (the objects).
Self is a programming language which implements what Lieberman defines. Self has objects which contain slots. Each slot can contain a method, or a reference to a prototype object in a parent slot. If an object received a message it didn't understand, it could delegate to an object in its parent slot.
This is prototypical inheritance, not class inheritance. It is similar, although not the same, as saying that an object has a class (like Ruby objects) which contains additional behavior.
Self giving us objects with a parent slot is he equivalent of JS giving us an object with a prototype reference.
JS is the most popular prototype-based language and is much easier for you to try, so rather than teach you Self, we'll look at JS. You can even open up your browser's console and try this out right now.
In JS we can assign a prototype: the equvalent of the parent slot in Self.
function Container(){};
Container.prototype = new Object();
Container.prototype.announce = function(){ alert("these are my things: " + this.things) };
function Bucket(things){this.things = things};
Bucket.prototype = new Container();
bucket = new Bucket("planes, trains, and automobiles")
bucket.announce() // alerts "these are my things: planes, trains, and automobiles"
In delegation parlance the bucket
object is the client which forwards a message to the delegate.
In the above example, you can see that the evaluation of this.things
was done in the context of the client object. When we called the announce
function, it was found on the delegate object. When the function was evaluated, this
refered to the client.
When a JS object's prototype contains a functon, the function is evaluated as if the object has that method. The first example showed that this
(which is just self
in JS) always referred to the original message recipient.
What about Ruby?
First, understand forwarding. Forwarding is passing a message from one object to another. The Ruby standard library forwardable
is aptly named and allows you to forward messages from one object to another.
Now, let's take the poorly named delegate
library from the Ruby standard library which also allows you to forward messages from one object to another.
require 'delegate' # assuming we have a Person class with a name method person = Person.new(:name => 'Jim') class Greeter < SimpleDelegator def hello "Hi! I'm #{name}." end end greeter = Greeter.new(person) greeter.hello #=> "Hi! I'm Jim."
What happens inside that Greeter
is that when the greeter
instance is initialized it contains a reference to the person
. When an unknown method is called, it is forwarded to the target object (which is person
). Remember, we're still looking at the delegate
library, which helps us forward messages... Confused? Yeah, I was too. So, it seems, is the rest of the world.
Forwarding is just the passing of a message to an object: a method call.
The difference between this and delegation is that these libraries allow you to easily forward messages to additional objects, rather than executing the method of another object in the context of the first like we saw with prototype-based inheritance in JS.
We are often barely aware of this because Ruby's method_missing
power does the trick in SimpleDelegator
. We think about methods automagically going to the object we want.
Even though our Greeter
method refers to self
, the message is forwarded to the other object and handled there when, of course, the method is missing on the client.
If we want to share behavior without extending an object with additional methods, then method_missing
and/or SimpleDelegator
can do the trick nicely. This works well for simple uses. It does, however, break references to an object's class.
Suppose, for example, we want to reference the client object's class with some new kind of greeting. And instead of the normal greeting, let's make it say "Hi! I'm the esteemed Person, Jim."
We don't want to completely rewrite the method, so we'll rely on super
to get whatever is implemented in the regular Greeter
.
class ProperGreeter < Greeter def name "the esteemed " + self.class.name + ", " + super end end proper_greeter = ProperGreeter.new(person) proper_greeter.hello #=> "Hi! I'm the esteemed ProperGreeter, Jim."
Hmmm. That's not what we expected. What we wanted to see was "the esteemed Person".
This is a simple illustration of the fact that we have two objects and is an example of self-schizophrenia in Object-oriented design. Each object has its own identity and notion of self
. We can fix that by changing the reference to use __getobj__
(the way SimpleDelegator expects) instead of self
, but this is an illustration of how self
does not refer to what we want. We must explicitly work with two objects and our mental model always requires that we think about two objects interacting when we only really care about altering the behavior of one.
This is not delegation. This is always two objects collaborating. Despite the numerous books, articles, and software libraries telling you otherwise. The Gang of Four got Delegation wrong, but that's OK because now you know better.
Whatever, I'll call it what I want
Who cares. Let's not rock the boat. Everyone is doing it.
Yeah, Design Patterns showed examples in C++. And C++ can't do delegation. So is that a valid argument for rewriting the meaning of a term?
If the language you use can't provide it, don't just call what the language can do "delegation."
Get the concepts right
Developing an application requires that you understand not only your business domain concepts, but also understand many tools and approaches to your design. Software design patterns are common ways to solve common problems. Understanding what they are and when to use them is a key part of being an efficient developer.
The Design Patterns book is often lauded for giving names to commonly used software design patterns. This helps all of us use the same terms when discussing our software and prevents confusion.
Of course, as I began research for Clean Ruby I picked up the Design Patterns book for some clarity. "Surely this book will help guide the discussion and understanding of how to approach different patterns," I thought. Sadly, it contains a glaring mistake that has been repeated and codified in numerous books, articles, and software libraries. Again and again I've read the same description of a behavior sharing and object composition pattern in software that is almost always given the wrong name.
The Design Patterns book is great for having pushed the idea of "composition over inheritance." In fact, so many developers seem to love this phrase that you'll probably be hit over the head with the term the next time you argue for setting up class-based inheritance. Thankfully, Sandi Metz defends class-based inheritance for its logical purposes in Practical Object Oriented Design in Ruby.
Design Patterns is often praised, not for innovation but, for its consolidation of terms. It guides us in the language that we use to understand and discuss different ways of approaching problems. It named for us common patterns so that we could better communicate with each other about what we are creating.
That praise, however, falls a little flat when you try to understand delegation.
My book, Clean Ruby, dives into understanding delegation and explores ways to keep your projects well origanized, maintainable, and loosly-coupled, giving you ways to share behavior and avoid cognitive overhead. Get it now and get your concepts straight.