Decorator Design Pattern

In the object-oriented world, simple applications usually require small classes with static behaviors. Adding, modifying, and sharing those behaviors can be achieved by mixing in modules or inheriting from other classes at compile time.

However, more complex applications might require a particular instance of a class to gain additional functionality at runtime. To modify the behavior of an object dynamically, we can utilize the decorator design pattern.

When to Decorate
Decoration can be used to add behavior to any individual object without affecting the behavior of other objects of the same class. Essentially the existing object is being wrapped with additional functionality.

Some practical problems that can be solved by decoration are
- applying one or more UI elements to a specific UI widget at runtime.
- saving an ActiveRecord model in various ways based on conditionals in a Rails controller.
- adding additional information to data streams by pre/appending with additional stream data.

Implementations of Decorators in Ruby
There are several ways to implement the decorator pattern in Ruby, but I cover my 4 favorite ways:
- Class + Method Missing decorator
- Module + Extend + Super decorator
- Plain Old Ruby Object decorator
- SimpleDelegator + Super + Getobj decorator

Class + Method Missing Decorator
The benefits of this implementation are:
- can be wrapped infinitely using Ruby instantiation.
- delegates through all decorators.
- can use the same decorator more than once on the same component.
- transparently uses component’s original interface.

The drawbacks of this implementation are:
- uses method_missing.
- the class of the decorated object is the decorator.

Sample example

sample.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
module Decorator
  def initialize(decorated)
    @decorated = decorated
  end

  def method_missing(meth, *args)
    if @decorated.respond_to?(meth)
      @decorated.send(meth, *args)
    else
      super
    end
  end

  def respond_to?(meth)
    @decorated.respond_to?(meth)
  end
end

class Coffee
  def cost
    2
  end
end

class Milk
  include Decorator

  def cost
    @decorated.cost + 0.4
  end
end

class Whip
  include Decorator

  def cost
    @decorated.cost + 0.2
  end
end

class Sprinkles
  include Decorator

  def cost
    @decorated.cost + 0.3
  end
end

Whip.new(Coffee.new).cost #=> 2.2
Sprinkles.new(Whip.new(Milk.new(Coffee.new))).cost #=> 2.9

# Factory class
class CoffeeFactory
  def self.cappuccino
    Sprinkles.new(Cream.new(Milk.new(Coffee.new)))
  end
end

CoffeeFactory.cappucino.kind_of? Coffee #=> true

Module + Extend + Super Decorator
The benefits of this implementation are:
- it delegates through all decorators.
- has all of the original interface because it is the original object.

The drawbacks of this implementation are:
- can not use the same decorator more than once on the same object.
- difficult to tell which decorator added the functionality.

Sample example

sample.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Coffee
  def cost
    2
  end
end

module Milk
  def cost
    super + 0.4
  end
end

module Sugar
  def cost
    super + 0.2
  end
end

coffee = Coffee.new
coffee.extend(Milk)
coffee.cost   #=> 2.4
coffee.extend(Sugar)
coffee.cost   #=> 2.6

Plain Old Ruby Object Decorator
The benefits of this implementation are:
- can be wrapped infinitely using Ruby instantiation.
- delegates through all decorators.
- can use same decorator more than once on component.

The drawbacks of this implementation are:
- cannot transparently use component’s original interface.

Sample example

sample.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Coffee
  def cost
    2
  end

  def origin
    "Cambodia"
  end
end

class Sugar
  def initialize(component)
    @component = component
  end

  def cost
    @component.cost + 0.2
  end
end

class Milk
  def initialize(component)
    @component = component
  end

  def cost
    @component.cost + 0.4
  end
end

coffee = Coffee.new
Sugar.new(Milk.new(coffee)).cost  #=> 2.6
Sugar.new(Sugar.new(coffee)).cost #=> 2.4
Sugar.new(Milk.new(coffee)).class #=> Sugar
Milk.new(coffee).origin           #=> NoMethodError

SimpleDelegator + Super + Getobj
The benefits of this implementation are:
- can be wrapped infinitely using Ruby instantiation.
- delegates through all decorators.
- can use same decorator more than once on component.
- transparently uses component’s original interface.
- class if the component.

The drawbacks of this implementation are:
- it redefines class.

Sample example

sample.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Coffee
  def cost
    2
  end

  def origin
    'Cambodia'
  end
end

require 'delegate'

class Decorator < SimpleDelegator
  def class
    __getobj__.class
  end
end

class Milk < Decorator
  def cost
    super + 0.4
  end
end

So far so good, Let decorate your way with Decorator Design Pattern. :)