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!





