Today we introduce the classic design pattern of decorator. If you’ve used Python, I don’t think I need to introduce you to decorators. Let’s look at an example:

import functools


def foo():
    print("=== foo ===")


if __name__ == "__main__":
    foo()

Copy the code

If we want to do something before or after foo (for example, in Web development, we often need to do this, for example, at the beginning of a request, we initialize a transaction, and at the end of the request, we try to commit or roll back the transaction) :

import functools


def with_tx(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("=== start tx ===")
        result = func(*args, **kwargs)
        print("=== commit tx ===")
        return result

    return wrapper


@with_tx
def foo():
    print("=== foo ===")


if __name__ == "__main__":
    foo()

Copy the code

Do this:

$ python main.py 
=== start tx ===
=== foo ===
=== commit tx ===

Copy the code

Let’s start with the syntax sugar of Python decorators. Why is it that after using with_tx, we can still call the decorated function in the name of foo? Let’s break the with_tx function into its parts:

def with_tx(func): # define the with_tx function that takes a function as an argument @functools.wraps(func) It is also a decorator def wrapper(*args, **kwargs): # Define a closure function that can use outer variables, and therefore func. Print ("=== start tx ===") Result = func(*args, **kwargs) print("=== commit tx ===") return result return wrapperCopy the code

So, we can draw the following conclusions:

  • With_tx takes a function as an argument, and it returns a function

  • The closure function inside with_tx is finally returned, and its implementation determines when the wrapped function is called

  • The function returned after @with_tx is called with the name foo, because it is equivalent to assigning the returned wrapper directly to foo, which is equivalent to foo = with_tx(foo).

This is the decorator pattern, a design pattern that adds a little functionality without changing the original code.

The Go language decorator pattern

After learning about the Decorator pattern in Python, let’s look at how Go implements the decorator pattern. It’s easy:

package main

import (
    "fmt"
)

type Decoer func(i int, s string) bool

func foo(i int, s string) bool {
    fmt.Printf("=== foo ===\n")
    return true
}

func withTx(fn Decoer) Decoer {
    return func(i int, s string) bool {
        fmt.Printf("=== start tx ===\n")
        result := fn(i, s)
        fmt.Printf("=== commit tx ===\n")

        return result
    }
}

func main() {
    foo := withTx(foo)
    foo(1, "hello")
}

Copy the code

Since Go does not have the syntax sugar found in Python, you have to manually reassign to variables of the same name.

This is the decorator pattern.

This article uses the Article Synchronization Assistant to synchronize