---
title: "SP2 - Decorator & Function Decorator & Closure - Learn Design Pattern From Simple Things"
description: "Ice cream gets better with extra flavor - and code gets richer with the Decorator pattern. Learn closures, function decorators, and object wrapping."
pubDate: "2022-11-29"
lang: "en"
slug: "sp2-decorator-function-decorator-closure-learn-design-pattern-from-simple-things"
url: "https://hoangyell.com/sp2-decorator-function-decorator-closure-learn-design-pattern-from-simple-things/"
author: "HoangYell"
categories: ["Software Craft"]
tags: ["design-patterns", "structural-patterns"]
heroImage: "https://raw.githubusercontent.com/HoangGeek/store/main/webp/jPpXTvG.webp"
---

_Decorator is a **structural** design pattern:_
🖤🖤🖤🖤🤍

>Don't be confused! Decorator and Function Decorator have no relationship at all. However, In Python when people talk about decorator, mostly they are talking about Function Decorator.

## What is Closure?

Closure is a technique of combining the callables (usually 2 functions) together `prepare_recipe, updated_func` that:

- The variables `layer_3, layer_2, layer_1` need to be stored for later usage in a specific scope. It will be passed to the outer/enclosing/wrapper function.

- The outer/enclosing/wrapper function `prepare_recipe` stores variables. The inner/nested function will use the variables later.

- The inner/nested function `updated_func` can access the variables thanks to the executed outer/enclosing/wrapper function.

>Similar to Closure's technique, we also have Function Decorator

## What is Function Decorator?

Function Decorator is a technique of combining the callables (usually 2 functions) together `prepare_recipe, updated_func` that:

- The origin/main function `decorate_ice_cream` is deprecated and needs to be updated a bit. It will be passed to the outer/enclosing/wrapper function. 

- The outer/enclosing/wrapper function `prepare_recipe` stores origin/main function. The inner/nested function will use the origin/main function later (Closure's usage).

- The inner/nested function `updated_func` will:
  - update behaviors around the origin/main function:
    - modify the input before giving the input to the origin/main function:
      - **original input:** `PopsicleIceCream`
      - **modified input:** `Almond(Condensed(Chocolate(` + `PopsicleIceCream`
    - modify the output of origin/main function before returning the final result:
      - **original output:** `Almond(Condensed(Chocolate(PopsicleIceCream`
      - **modified output:** `Almond(Condensed(Chocolate(PopsicleIceCream` + `))Milk)`
  - then return the new version of the origin/main function.

## What is Decorator?

Decorator help to add/update the object `PopsicleIceCream` dynamically by placing them inside the decorators `ChocolateDecorator, CondensedMilkDecorator, AlmondDecorator`.

It's like a Function Decorator, but here it applies to Class Instance.

![Image](https://raw.githubusercontent.com/HoangGeek/store/main/webp/205496812-911f3c64-23ec-4df2-97a0-c5f3f3f20451.webp)

## Why use Closure?

Closure creates an environment to remember simple values `layer_3, layer_2, layer_1`, it's like the class's usage!

Do you know `functools.partial`? `functools.partial` function is also an example of Closure's usage.

**Question:** _Class is the alternative to Closure, then when should we use Closure in lieu of Class?_

**Answer:** _For simplicity, you should use Class for all cases. Closure is just a technique in theory, it's probably only mentioned a lot in pointless interviews. I'm still bitter about that interview_ -_-

## Why use Function Decorator?

It helps reduce code duplication by wrapping common code into a bundle `def prepare_recipe`, then simply adding `@prepare_recipe` above the concrete functions. This makes the function's code shorter and easier to update/refactor.

**Question:** _What are the alternatives if I'm too lazy to create the Function Decorator?_

**Answer:** _You can make a long code with duplication or a little better solution is to split the big function into small functions, the choice is yours! However, let's reconsider using Function Decorator! Business before pleasure!_

## Why use Decorator?

Easily transform the original `popsicle` object into a `almond_condensed_milk_chocolate_popsicle` multiple features object dynamically, without affecting anything to the origin.

**Question:** _I can easily add more features to an object without using Decorator, right?_

**Answer:** _Of course, but Decorator is lovely in its own way because it keeps the code clean by wrapping an `popsicle` object in new `chocolate_popsicle` objects and repeating that `condensed_milk_chocolate_popsicle, almond_condensed_milk_chocolate_popsicle` process endlessly without touching the old code!_

## When to use Closure?

**Question:** _When should I use Closure?_

**Answer:** _When it's necessary to create an instance in order to store values `layer_3, layer_2, layer_1` and use `decorate_ice_cream` those values later_

### Input:
``` python
popsicle_ice_cream = "PopsicleIceCream"
cone_ice_cream = "IceCreamCone"
print("[original ice creams] ")
print(popsicle_ice_cream)
print(cone_ice_cream)

# main function
def decorate_ice_cream(layer_3, layer_2, layer_1, ice_cream):
    magic_number = 9
    layer_2_a, layer_2_b = layer_2[:magic_number], layer_2[magic_number:]
    decorated_ice_cream = f"{layer_3}({layer_2_a}({layer_1}({ice_cream})){layer_2_b})"
    return decorated_ice_cream
```
### Expected Output:
``` shell
[original ice creams] 
PopsicleIceCream
IceCreamCone

[decorated ice creams] 
Almond(Condensed(Chocolate(PopsicleIceCream))Milk)
Almond(Condensed(Chocolate(IceCreamCone))Milk)
```

## When to use Function Decorator?

**Question:** _When should I use Function Decorator?_

**Answer:** _When the environment values, input values, output values surrounding the main/deprecated function needs to be updated before and after execution._

### Input:
``` python
popsicle_ice_cream = "PopsicleIceCream"
cone_ice_cream = "IceCreamCone"
print("[original ice creams] ")
print(popsicle_ice_cream)
print(cone_ice_cream)

# main function
def decorate_ice_cream(ice_cream):
    return ice_cream
```
### Expected Output:
``` shell
[original ice creams] 
PopsicleIceCream
IceCreamCone

[decorated ice creams] 
Almond(Condensed(Chocolate(PopsicleIceCream))Milk)
Almond(Condensed(Chocolate(IceCreamCone))Milk)
```

## When to use Decorator?

**Question:** _When should I use GoF Decorator?_

**Answer:** _When object properties need to be updated at runtime, but the Class structure remains origin._

### Input:
``` python
class IceCream:
    def taste(self):
        return "IceCream"

class PopsicleIceCream(IceCream):
    def taste(self):
        return "PopsicleIceCream"

class ConeIceCream(IceCream):
    def taste(self):
        return "ConeIceCream"

popsicle = PopsicleIceCream()
cone = ConeIceCream()
print("[original ice creams] ")
print(popsicle.taste())
print(cone.taste())
```
### Expected Output:
``` shell
[original ice creams] 
PopsicleIceCream
ConeIceCream

[decorated ice creams] 
Almond(Condensed(Chocolate(PopsicleIceCream))Milk)
Almond(Condensed(Chocolate(ConeIceCream))Milk)
```

## How to implement Closure?

### Non-Closure Implementation:
``` python
class Recipe:
    def __init__(self, func, layer_3, layer_2, layer_1):
        self.func = func
        self.layer_3 = layer_3
        self.layer_2 = layer_2
        self.layer_1 = layer_1

    def decorate_ice_cream(self, ice_cream):
        return self.func(self.layer_3, self.layer_2, self.layer_1, ice_cream)

if __name__ == "__main__":
    recipe = Recipe(decorate_ice_cream, "Almond", "CondensedMilk", "Chocolate")
    almond_condensed_milk_chocolate_popsicle = recipe.decorate_ice_cream(popsicle_ice_cream)
    almond_condensed_milk_chocolate_cone = recipe.decorate_ice_cream(cone_ice_cream)
    print("\n[decorated ice creams] ")
    print(almond_condensed_milk_chocolate_popsicle)
    print(almond_condensed_milk_chocolate_cone)
```

### Closure Implementation:
``` python
# closure
def prepare_recipe(func, layer_3, layer_2, layer_1):
    def updated_func(ice_cream):
        return func(layer_3, layer_2, layer_1, ice_cream)

    return updated_func

if __name__ == "__main__":
    # closure's outcome: updated function
    recipe = prepare_recipe(decorate_ice_cream, "Almond", "CondensedMilk", "Chocolate")
    almond_condensed_milk_chocolate_popsicle = recipe(popsicle_ice_cream)
    almond_condensed_milk_chocolate_cone = recipe(cone_ice_cream)
    print("\n[decorated ice creams] ")
    print(almond_condensed_milk_chocolate_popsicle)
    print(almond_condensed_milk_chocolate_cone)
```

[Source Code: Closure](https://github.com/HoangYell/Design-Pattern-From-Simple-Things/blob/main/sp2_closure.py)

## How to implement Function Decorator?

### Non-Function Decorator Implementation:
``` python
# main function
def decorate_ice_cream(ice_cream):
    magic_number = 9
    layer_3, layer_2, layer_1 = "Almond", "CondensedMilk", "Chocolate"
    layer_2_a, layer_2_b = layer_2[:magic_number], layer_2[magic_number:]
    front_ice_cream = f"{layer_3}({layer_2_a}({layer_1}("
    back_ice_cream = f")){layer_2_b})"
    half_decorated_ice_cream = front_ice_cream + ice_cream
    decorated_ice_cream = half_decorated_ice_cream + back_ice_cream
    return decorated_ice_cream

if __name__ == "__main__":
    almond_condensed_milk_chocolate_popsicle = decorate_ice_cream(popsicle_ice_cream)
    almond_condensed_milk_chocolate_cone = decorate_ice_cream(cone_ice_cream)
    print("\n[decorated ice creams] ")
    print(almond_condensed_milk_chocolate_popsicle)
    print(almond_condensed_milk_chocolate_cone)
```

### Function Decorator Implementation:

``` python
# decorator
def prepare_recipe(func):
    def updated_func(ice_cream):
        magic_number = 9
        layer_3, layer_2, layer_1 = "Almond", "CondensedMilk", "Chocolate"
        layer_2_a, layer_2_b = layer_2[:magic_number], layer_2[magic_number:]
        front_ice_cream = f"{layer_3}({layer_2_a}({layer_1}("
        back_ice_cream = f")){layer_2_b})"
        half_decorated_ice_cream = func(front_ice_cream + ice_cream)
        decorated_ice_cream = half_decorated_ice_cream + back_ice_cream
        return decorated_ice_cream

    return updated_func

@prepare_recipe
# main function
def decorate_ice_cream(ice_cream):
    return ice_cream

if __name__ == "__main__":
    almond_condensed_milk_chocolate_popsicle = decorate_ice_cream(popsicle_ice_cream)
    almond_condensed_milk_chocolate_cone = decorate_ice_cream(cone_ice_cream)
    print("\n[decorated ice creams] ")
    print(almond_condensed_milk_chocolate_popsicle)
    print(almond_condensed_milk_chocolate_cone)
```

[Source Code: Function Decorator](https://github.com/HoangYell/Design-Pattern-From-Simple-Things/blob/main/sp2_function_decorator.py)

### How to implement Function Decorator With Params?

#### Theory about Function Decorator without params:

**A) If we have:**

```python
def decorate_ice_cream(ice_cream):
    return ice_cream
```

**B) We can create decorator like this:**

```python
def prepare_recipe(func):
    def updated_func(ice_cream):
        return ... func(...) ...
    return updated_func
```

**C) Then apply decorator to the main function by:**

```python
@prepare_recipe
def decorate_ice_cream(ice_cream):
    return ice_cream
```
or
```python
decorate_ice_cream = prepare_recipe(decorate_ice_cream)
```

#### Theory about Function Decorator with params:

Those params need to be stored. It's time for **Closure** to show off!
- ```prepare_recipe = prepare_recipe_with_params(layer_3, layer_2, layer_1)```
- _Function Decorator With Params = Closure + Function Decorator Without Params_

**A) If we have:**

```python
def decorate_ice_cream(ice_cream):
    return ice_cream
```

**B) We can create decorator like this:**

closure will create environment to store `layer_3, layer_2, layer_1`

```python
# closure
def prepare_recipe_with_params(layer_3, layer_2, layer_1):
    # decorator
    def prepare_recipe(func):
        def updated_func(ice_cream):
            return ... func(...) ...
        return updated_func
    return prepare_recipe
```

**C) Then apply decorator to the main function by:**

```python
@prepare_recipe(layer_3, layer_2, layer_1)
def decorate_ice_cream(ice_cream):
    return ice_cream
```
or
```python
# closure's outcome:
prepare_recipe = prepare_recipe_with_params(layer_3, layer_2, layer_1)
decorate_ice_cream = prepare_recipe(decorate_ice_cream)
```

#### Function Decorator With Params Implementation:

``` python
# decorator with params
def prepare_recipe_with_params(layer_3, layer_2, layer_1):
    def prepare_recipe(func):
        def updated_func(ice_cream):
            magic_number = 9
            layer_2_a, layer_2_b = layer_2[:magic_number], layer_2[magic_number:]
            front_ice_cream = f"\{layer_3\}(\{layer_2_a\}(\{layer_1\}("
            back_ice_cream = f"))\{layer_2_b\})"
            half_decorated_ice_cream = func(front_ice_cream + ice_cream)
            decorated_ice_cream = half_decorated_ice_cream + back_ice_cream
            return decorated_ice_cream

        return updated_func

    return prepare_recipe

@prepare_recipe_with_params("Almond", "CondensedMilk", "Chocolate")
# main function
def decorate_ice_cream(ice_cream):
    return ice_cream

if __name__ == "__main__":
    almond_condensed_milk_chocolate_popsicle = decorate_ice_cream(popsicle_ice_cream)
    almond_condensed_milk_chocolate_cone = decorate_ice_cream(cone_ice_cream)
    print("\n[decorated ice creams] ")
    print(almond_condensed_milk_chocolate_popsicle)
    print(almond_condensed_milk_chocolate_cone)
```

[Source Code: Function Decorator With Params Manually](https://github.com/HoangYell/Design-Pattern-From-Simple-Things/blob/main/sp2_function_decorator_params_manual_call.py)

[Source Code: Function Decorator With Params](https://github.com/HoangYell/Design-Pattern-From-Simple-Things/blob/main/sp2_function_decorator_params.py)

## How to implement Decorator?

### Non-Decorator Implementation:

```python
def generate(ice_cream, flavor):
    old_taste = ice_cream.taste()
    if "Chocolate" == flavor:

        def _taste_chocolate():
            return f"Chocolate(\{old_taste\})"

        ice_cream.taste = _taste_chocolate
    elif "CondensedMilk" == flavor:

        def _taste_condensed_mild():
            return f"Condensed(\{old_taste\})Milk"

        ice_cream.taste = _taste_condensed_mild
    elif "Almond" == flavor:

        def _taste_almond():
            return f"Almond(\{old_taste\})"

        ice_cream.taste = _taste_almond
    return ice_cream

if __name__ == "__main__":
    chocolate_popsicle = generate(popsicle, "Chocolate")
    chocolate_cone = generate(cone, "Chocolate")
    condensed_milk_chocolate_popsicle = generate(chocolate_popsicle, "CondensedMilk")
    condensed_milk_chocolate_cone = generate(chocolate_cone, "CondensedMilk")
    almond_condensed_milk_chocolate_popsicle = generate(
        condensed_milk_chocolate_popsicle, "Almond"
    )
    almond_condensed_milk_chocolate_cone = generate(
        condensed_milk_chocolate_cone, "Almond"
    )
    print("\n[decorated ice creams] ")
    print(almond_condensed_milk_chocolate_popsicle.taste())
    print(almond_condensed_milk_chocolate_cone.taste())
```

### Decorator Implementation:

```python
class Decorator(IceCream):
    _component = None

    def __init__(self, component):
        print(f"\{type(self)\} is decorating the \{type(component)\}")
        self._component = component

    @property
    def component(self):
        return self._component

    def taste(self):
        return self._component.taste()

class ChocolateDecorator(Decorator):
    def taste(self):
        return f"Chocolate(\{self.component.taste()\})"

class CondensedMilkDecorator(Decorator):
    def taste(self):
        return f"Condensed(\{self.component.taste()\})Milk"

class AlmondDecorator(Decorator):
    def taste(self):
        return f"Almond(\{self.component.taste()\})"

if __name__ == "__main__":
    chocolate_popsicle = ChocolateDecorator(popsicle)
    chocolate_cone = ChocolateDecorator(cone)
    condensed_milk_chocolate_popsicle = CondensedMilkDecorator(chocolate_popsicle)
    condensed_milk_chocolate_cone = CondensedMilkDecorator(chocolate_cone)
    almond_condensed_milk_chocolate_popsicle = AlmondDecorator(
        condensed_milk_chocolate_popsicle
    )
    almond_condensed_milk_chocolate_cone = AlmondDecorator(
        condensed_milk_chocolate_cone
    )
    print("\n[decorated ice creams] ")
    print(almond_condensed_milk_chocolate_popsicle.taste())
    print(almond_condensed_milk_chocolate_cone.taste())
```

[Source Code: Decorator](https://github.com/HoangYell/Design-Pattern-From-Simple-Things/blob/main/sp2_gof_decorator.py)
