The decorator pattern allows to enrich objects functionality dynamically. Thus excessive inheritance can be avoided in many cases.
Decorators also are often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern.
Imagine a table, which can be decorated. If the table was set, it is a dining table. If a laptop was put on it, it is clearly a desk.
Products having a price is another simple example:
class Product attr_accessor :price def initialize price @price = price end end
Each products total price is calculated different, depending on the context. It includes the base price, but also can grant a discount or can add taxes and so on.
Sure, the story also could be mapped with some logic in the Product class itself or handled by inheritance. But there is no doubt that both approaches will be awkward very soon.
It is more reasonable to leave the Product class and create decorators for the particular use cases:
class TaxDecorator TAX_FACTOR = 1.2 def initialize object @object = object end def price @object.price * TAX_FACTOR end end class DiscountDecorator attr_reader :discount_factor DISCOUNT_FACTOR = 0.9 def initialize object @object = object end def price @object.price * discount_factor end private def discount_factor @discount_factor || DEFAULT_DISCOUNT_FACTOR end end
The decorator interface has to meet the target object (the product) API for sustaining the entire functionality.
The products price can be changed dynamically, like:
shirt = Product.new 10 shirt.price # => 10 TaxDecorator.new(shirt).price # => 12.0 DiscountDecorator.new(shirt).price # => 9.0
Basically each decorator wraps the target object and adds new behavior.
Of course decorators also can be combined with each other:
tv = Product.new 200 TaxDecorator.new(DiscountDecorator.new tv).price # => 216.0
Additionally other classes can be decorated, if they sustain the expected interface:
class Employee def initialize name, annual_salary @name = name @salary = annual_salary end def price @salary / 1600 # ca. 1.600 man hours per year end end
Then Bobs man hour costs at least:
bob = Employee.new 'Bob', 50000 bob.price # => 31.25 TaxDecorator.new(bob).price # => 37.2