I think by now we all know to prefer composition over inheritance. But in a language with a lot of options, what’s the best kind of composition to use?

Composing an adventure

Consider an adventure game, with objects representing player characters.

class Character
  # ...
end

A Character can be described:

class Character
  # ...
  def describe
    puts "You are a dashing, rugged adventurer."
  end
  # ...
end

A Character can look, listen, and smell his environment:

class Character
  # ...
  def look
    list("You can see", ["a lightning bug", "a guttering candle"])
  end

  def listen
    list("You hear", ["a distant waterfall"])
  end

  def smell
    list("You smell", ["egg salad"])
  end

  def list(prefix, objects)
    objects.each do |o|
      puts "#{prefix} #{o}."
    end
  end
  # ...
end
require './decoration-vs-extension'
cohen = Character.new
cohen.describe
cohen.look
cohen.listen
You are a dashing, rugged adventurer.
You can see a lightning bug.
You can see a guttering candle.
You hear a distant waterfall.

The character can also consult all of his senses at once:

class Character
  # ...
  def observe
    look
    listen
    smell
  end
  # ...
end
require './decoration-vs-extension'
cohen = Character.new
cohen.observe
You can see a lightning bug.
You can see a guttering candle.
You hear a distant waterfall.
You smell egg salad.

Characters can have various effects conferred upon them by items, potions, etc. A simple example is a hat:

require 'delegate'
class BowlerHatDecorator < SimpleDelegator
  def describe
    super
    puts "A jaunty bowler cap sits atop your head."
  end
end

At each turn of the game, the Character object will be decorated with whatever effects are currently active, and then a user command will be performed:

require './decoration-vs-extension'
cohen = BowlerHatDecorator.new(Character.new)
cohen.describe
You are a dashing, rugged adventurer.
A jaunty bowler cap sits atop your head.

Seeing in the dark

A more interesting effect is conferred by an infravision potion. It enables your character to see in the dark.

class InfravisionPotionDecorator < SimpleDelegator
  def describe
    super
    puts "Your eyes glow dull red."
  end

  def look
    super
    look_infrared
  end

  def look_infrared
    list("You can see", ["the ravenous bugblatter beast of traal"])
  end
end

While the character is experiencing the effects of an infravision potion, his powers of observation increase:

require './decoration-vs-extension'
cohen = InfravisionPotionDecorator.new(Character.new)
cohen.describe
cohen.look
You are a dashing, rugged adventurer.
Your eyes glow dull red.
You can see a lightning bug.
You can see a guttering candle.
You can see the ravenous bugblatter beast of traal.

There’s just one little problem that crops up when the #observe method is called.

require './decoration-vs-extension'
cohen = InfravisionPotionDecorator.new(Character.new)
cohen.observe
You can see a lightning bug.
You can see a guttering candle.
You hear a distant waterfall.
You smell egg salad.

Hey, where’d that bugblatter beast go?

The Character#observe method calls #look—but since the wrapped object has no knowledge whatsoever of the InfravisionPotionDecorator, it calls the original definition of #look, not the one which also calls #look_infrared.

Now, granted, this flaw actually works out in our intrepid adventurer’s favor, since the ravenous bugblatter beast of Traal is so stupid it thinks that if you can’t see it, it can’t see you. But never mind that: it’s still a bug, and bugs must be blattered.

A solution that’s all wet

We could patch this flaw by overriding #observe as well in the decorator:

class InfravisionPotionDecorator < SimpleDelegator
  def observe
    look
    listen
    smell
  end
end

Yuck! This is the exact same implementation as in Character, just copied and pasted so that the correct implementaiton of #look will be called. Clearly this is non-DRY. But even worse, we’ve introduced a nasty variety of connascence. Every time we introduces a new Character method which calls #look, we’ll have to cull through every single effect decorator which overrides #look, adding copy-and-pasted versions of the new method so that it doesn’t accidentally ignore the effect-wrapped version. Double yuck!

Modules to the rescue

In Ruby, there is an easy solution: extend the character with a module instead of a decorator.

module InfravisionPotionModule
  def describe
    super
    puts "Your eyes glow dull red."
  end

  def look
    super
    look_infrared
  end

  def look_infrared
    list("You can see", ["the ravenous bugblatter beast of traal"])
  end
end
require './decoration-vs-extension'
cohen = Character.new.extend(InfravisionPotionModule)
cohen.observe
You can see a lightning bug.
You can see a guttering candle.
You can see the ravenous bugblatter beast of traal.
You hear a distant waterfall.
You smell egg salad.

This time the overridden method is added directly to the object via its singleton class. So even the object’s own unmodified methods get the new infravision version of #look.

Sadly, by enabling him to see the monster we have sealed our protagonists’s fate. But at least we fixed the bug!

Other solutions

That’s not the only way to fix the problem. We might, for instance, decompose our Character into individual body parts, with separate attributes for eyes, nose, and ears. The Character could then delegate the individual senses to their respective organs:

require 'forwardable'
class Character
  extend Forwardable

  attr_accessor :eyes
  attr_accessor :ears
  attr_accessor :nose

  def_delegator :eyes, :look
  def_delegator :ears, :lisen
  def_delegator :nose, :smell
end

A potion of infravision might then replace the character’s eyes with infrared-enhanced ones:

class InfravisionPotionDecorator < SimpleDelegator
  class EyesDecorator < SimpleDelegator
    # ...
  end

  def initialize(character)
    super(character)
    character.eyes = EyesDecorator.new(character.eyes)
  end
end

…but this is an awful lot of code and ceremony. It might make sense someday, but right now it feels like massive overkill. The module extension approach, by contrast, is only a small change from our original version.

Are decorators overrated?

So what can we learn from this? When composing objects, Is it always better to use module extension than decoration?

In a word, no. For one thing, decoration is a simpler structure to understand. Given object A wrapped in object B wrapped in object C, it’s easy to reason about how method calls will be handled. They’ll always go one-way: a method in object A will never reference a method in B or C.By contrast, method calls in a module-extended object can bounce around the inheritance heirarchy in unexpected ways.

A second consideration is that once you’ve extended an object with a module, its behavior is changed for all clients, including itself. You can’t interact with the “unadorned” object anymore. You might extend an object for your own purposes, then pass it to a third-party method which doesn’t understand the modified behavior of the object and barfs as a result.

Finally, there’s a performance penalty. While it varies from implementation to implementation, dynamically extending objects can slow down your code as a result of the method cache beign invalidated. Of course, as with all performance-related guidelines, be sure to profile before making any code changes based on this point.

Conclusion

Decoration and module extension are both viable ways to compose objects in Ruby. Which to use is not a simple black-or-white choice; it depends on the purpose of the composition.

For applications where you want to adorn an object with some extra functionality, or modify how it presents itself, a decorator is probably the best bet. Decorators are great for creating Presenters, where we just want to change an object’s “face” in a specific context.

On the other hand, when building up a composite object at runtime object out of individual “aspects” or “facets”, module extension may make more sense. Judicious use of module extension can lead to a kind of “emergent behavior” which is hard to replicate with decoration or delegation.

At least, this has been my experience. Got some experiences or opinions on decoration vs. module extension? Feel free to leave a note in the comments!

Published by Avdi Grimm

15 Comments

  1. Good rundown of the trade-offs.  I find that I jump to your higher-ceremony strategy/decorator pattern sooner with less reluctance than most ruby devs (although perhaps slower than Java devs?).  I’m more likely to jump straight from the (simple) delegator all the way to the more complicated strategy pattern.

    The critical problem with the low ceremony module mixin is that it can’t be un-mixed.  Most potions’ effects are temporary, and they leave the bloodstream eventually.  You could have the potion module keep track of this state, but I’d much rather remove a stateless potion from the (stateful) character than leave an inert potion mixed in.  Another mechanism would be to use stateless characters, destroying and remaking them whenever they change, but down that road lies madness (or perhaps clojure); and anyway that’s just pushing the state pattern up to a higher level in the architecture.

    This, of course, is perfectly suited to the GoF’s Strategy and State paterns, but (as you say), they often feel like more high ceremony than is worth it, even if they map better to the domain’s structure/semantics.  I seem to recall that someone had a library that simulated mixin and mixout of modules.  If so, then that would be a (relatively) low-ceremony way to get the biggest benefits of the Strategy and State patterns.  What do you think?

    Reply
    • There are indeed some mix/unmix tools, but I’m a trifle dubious of them. As the article says, in this notional game the player object is built up once for every turn, so long-lived objects are not a problem. For long-lived objects we definitely need a different design.

      Reply
      • Ah, I missed that: “At each turn of the game, the Character object will be decorated with whatever effects”; the clojure route.  🙂  It’s not really that mad, the more I think on it.

        Reply
      • Why are you dubious about mix/unmix? It seems to me that a long-lived object should be able to add and subtract modules at will (for example, I like the idea of subtracting an ORM module from a model object when it gets passed from controller to view in Rails).

        Reply
    • I think you’re thinking of Mixology … which looks like it hasn’t been touched since mid 2009.  That might be a sign that it “works”, but probably not with ruby 1.9.  Mixing out is unfortunately very likely to be highly implementation (of ruby) dependant…

      And due to the mixin callbacks (extended and included) I could easily conceive of a situation where mixout ends badly.  (module B conditionally defines methods from module A, which is then mixed-out.  Hijinks ensue).

      I’d love it if it would actually work, but I think mixout is one of those things that has to be dealt with at the language spec level ….

      Reply
  2. Once again, thank you for putting together this kind article – very helpful.

    Reply
  3. Just as you couldn’t help writing this article, I couldn’t help trying to implement it.  I noticed you left the tests out of this piece.  That’s one of the reasons I shy away from module extension (which, by the way is a kind of inheritance 🙂 ).  When the methods are on the same object, testing the interaction between the pieces of logic can get hairy.  At least that’s my experience, so I wanted to put it to the test.

    https://github.com/Peeja/potion_of_infravision

    Nothing revelatory, but it made for a good exercise.  It was a nice solo CodeRetreat.  I encourage others to try the same in other styles.

    Reply
  4. Ah, good ol’ LPC days. Thanks for the reminder: we implemented extra features of items with modules (we hadn’t call it mixins, the term was not yet coined). Like if you implemented a torch, you could extend Item class with M_Igniteable. It implemented ignite and extinguish command callbacks, and added a hook on item description (in LPC you don’t have super) among other things.

    At the time you could learn a ton by just reading mud standard libraries 🙂

    Dave Fasthand @ After the Plague mud, retired head admin

    Reply
  5. Just as I was reading up on self/object schizophrenia and object teams, you post this. Spooky! paranoid

    Reply
  6. My first impression is the variability addressed by the decoration/extension in this example would be better served by some ‘business logic/rules’ inside or outside of the class.  I see the delegation (and likely the extension) as necessary to compensate for display rules that are implicit rather than explicit.  Personally I would probably define these rules within a separate Environment class which would be passed as a param to each of the defined character methods (and probably would involve a double dispatch call to the Environment with the Character as a param).  The Environment would contain the rules about how characters appeared based on their attributes, and the Character would interact with the Environment to determine what it could see.  These rules could of course also be done all within the Character class also.  I realize that I’m probably off in a small, small minority on this one…

    Reply
  7. Holy crap, SimpleDelegator takes 2 seconds to decorate 200 objects!!

    Reply
  8. Just FYI, his part confused me for awhile, “The Character#observe method calls #look—but since the wrapped object has no knowledge whatsoever of the InfravisionPotionDecorator…”

    It’s much clearer to me if the “wrapped object has no knowledge whatsoever” part is removed. Something like this: “The Character#observe method calls #look but it calls the original definition of #look, not the one which also calls #look_infrared.”

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *