Decorator Design Pattern in Swift

1. Definition

The Decorator design pattern is a structural pattern that allows you to add new behaviors to objects dynamically by placing them inside special wrapper objects. These wrappers are called decorators. Instead of modifying the object itself, you encapsulate the original object inside an abstract interface and then provide multiple decorator classes to extend its capabilities.

2. Problem Statement

Suppose you're building a coffee shop application where you sell different types of beverages, and customers can add various condiments like milk, sugar, whipped cream, etc. If you were to create a class for every possible combination, it would result in a massive class hierarchy and a lot of redundancy.

3. Solution

Rather than creating a class for each combination, you can create a base Beverage class and then extend its functionality using decorators for each condiment. Each decorator wraps the original object and can add its behavior.

4. Real-World Use Cases

1. Graphical window toolkits might use decorators to add functionalities like borders or scrollbars.

2. Middleware libraries (e.g., logging, monitoring, caching) that want to augment the behavior of objects without modifying them.

5. Implementation Steps

1. Define a common interface for both your primary objects and the decorators.

2. Create a concrete component class to represent the primary objects.

3. For every different type of behavior, create a concrete decorator class that wraps a component object and adds the desired functionality.

6. Implementation in Swift Programming

// Step 1: Common interface
protocol Beverage {
    func cost() -> Double
    func description() -> String
}
// Step 2: Concrete component
class Coffee: Beverage {
    func cost() -> Double {
        return 5.0
    }
    func description() -> String {
        return "Coffee"
    }
}
// Step 3: Concrete decorators
class MilkDecorator: Beverage {
    private let beverage: Beverage
    init(beverage: Beverage) {
        self.beverage = beverage
    }
    func cost() -> Double {
        return beverage.cost() + 1.0
    }
    func description() -> String {
        return beverage.description() + ", Milk"
    }
}
class SugarDecorator: Beverage {
    private let beverage: Beverage
    init(beverage: Beverage) {
        self.beverage = beverage
    }
    func cost() -> Double {
        return beverage.cost() + 0.5
    }
    func description() -> String {
        return beverage.description() + ", Sugar"
    }
}
// Usage:
let simpleCoffee = Coffee()
let coffeeWithMilk = MilkDecorator(beverage: simpleCoffee)
let coffeeWithMilkAndSugar = SugarDecorator(beverage: coffeeWithMilk)
print(coffeeWithMilkAndSugar.description())

Output:

Coffee, Milk, Sugar

Explanation:

1. We started with a Beverage protocol, the common interface for all beverages and their decorators.

2. Coffee is a primary object that implements the Beverage protocol.

3. The MilkDecorator and SugarDecorator classes are decorators that also implement the Beverage protocol. They maintain a reference to a Beverage object and can add their behavior to it.

4. In our usage example, we first create a Coffee object, then wrap it in a MilkDecorator, and finally wrap it again in a SugarDecorator. The final object adds milk and sugar to the coffee, as seen in the output.

7. When to use?

Use the Decorator pattern when:

1. You need to add responsibilities to objects dynamically and transparently without affecting other objects.

2. Subclassing is impractical because it produces an exponential number of subclasses for every new feature.

3. You want to keep the new functionalities separated and organized.


Comments