Decorator (aka. Wrapper) allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class.

Structure

Class diagram (UML):

Decorator Class Diagram (UML)

Object diagram (UML):

Decorator Object Diagram (UML)

When Would You Use It?

  • You want to add responsibility to individual objects dynamically and transparently without affecting other objects.
  • You want to extend the behavior of a class that is impractical to do so. A class definition may be hidden and unavailable for subclassing—or each behavioral extension to a class would require a tremendous amount of subclasses to support each combination of features.
  • Extended responsibilities of a class can be optional.

Related Patterns

Design PatternNotes
AdapterA decorator is different from an adapter in that a decorator only changes an object’s responsibilities, not its interface; an adapter will give an object a completely new interface.
CompositeA decorator can be viewed as a degenerate composite with only one component. However, a decorator adds additional responsibilities—it isn’t intended for object aggregation.
StrategyA decorator lets you change the skin of an object; a strategy lets you change the guts. These are two alternative ways of changing an object.

Discussion

Decorator vs. Strategy

Also known as Changing the “Skin” vs. “Guts” of an Object.

These are two alternative ways of changing an object. Here are some comparison:

  • Strategies are a better choice in situations where the Component class is intrinsically heavyweight.
  • The decorators are transparent to the component.
  • With strategies, the component itself knows about possible extensions.
  • A strategy can have its own specialized interface, whereas a decorator’s interface must conform to the component’s.

References

  • Design Patterns: Elements of Reusable Object-Oriented Software, by Book by Erich Gamma, John Vlissides, Ralph Johnson, and Richard Helm (GoF)
  • Pro Objective-C Design Patterns for iOS, by Carlo Chung
  • Pro Design Patterns in Swift, by Adam Freeman

Example #1 – Coffee Ingredients

A basic coffee (the “component”) can add different ingredients (decorations), such as milk, cream, whip, etc.

  • It is freely to add any of these.
  • For each of these, when added, an extra charge will apply.

Here is the class diagram:

Decorator Class Diagram (UML) - Graphic Mark

Code

import UIKit

// Protocol for "coffee product" (Component)
protocol CoffeeProduct {
    var price: Double {get set}
    var name: String {get set}

    func getCost() -> Double
    func getIngredient() -> String
}

// Class for "coffee" (ConcreteComponent)
class Coffee: CoffeeProduct {

    var name = "Coffee"
    var price = 10.0

    func getCost() -> Double {

        return price
    }

    func getIngredient() -> String {

        return name
    }
}


// Abstract class for "coffee ingredients" (Decorator)
class CoffeeDecorator: CoffeeProduct {

    private let decoratedCoffeeProduct: CoffeeProduct

    var name = ""
    var price = 0.0

    required init(decoratedCoffeeProduct: CoffeeProduct) {

        self.decoratedCoffeeProduct = decoratedCoffeeProduct
    }

    func getCost() -> Double {

        return decoratedCoffeeProduct.getCost()
    }

    func getIngredient() -> String {

        return decoratedCoffeeProduct.getIngredient()
    }
}

// Class for each "coffee ingredient" (ConcreteDecorator)
class MilkIngredient: CoffeeDecorator {

    required init(decoratedCoffeeProduct: CoffeeProduct) {

        super.init(decoratedCoffeeProduct: decoratedCoffeeProduct)

        // modify the stored properties
        self.name = "Milk"
        self.price = 3.0
    }

    override func getCost() -> Double {

        return super.getCost() + price
    }

    override func getIngredient() -> String {

        return super.getIngredient() + " + (name)"
    }
}

class CreamIngredient: CoffeeDecorator {

    required init(decoratedCoffeeProduct: CoffeeProduct) {

        super.init(decoratedCoffeeProduct: decoratedCoffeeProduct)

        // modify the stored properties
        self.name = "Cream"
        self.price = 2.0
    }

    override func getCost() -> Double {

        return super.getCost() + price
    }

    override func getIngredient() -> String {

        return super.getIngredient() + " + (name)"
    }
}

// Usage
var aCoffee: CoffeeProduct = Coffee()
println("Current coffee product: (aCoffee.getIngredient()), with the cost: (aCoffee.getCost())")
aCoffee = MilkIngredient(decoratedCoffeeProduct: aCoffee)
println("Current coffee product: (aCoffee.getIngredient()), with the cost: (aCoffee.getCost())")
aCoffee = CreamIngredient(decoratedCoffeeProduct: aCoffee)
println("Current coffee product: (aCoffee.getIngredient()), with the cost: (aCoffee.getCost())")

Leave a Reply

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

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>