Elixir: 函數裝飾器

Java 模式裏面有一個概念叫「對修改關閉, 對擴展開放」, 這是一個面向對象的組件可重用原則. 裝飾器就是實現該原則的一個經典實例.git

裝飾器原理

經過符號註解的方式, 給被註解的函數或對象添加新功能, 重寫現有的功能, 而又不對現有的代碼作變化的一種方法. 它對使用者是透明的. 經過裝飾器能夠實現的經常使用功能包括:github

  • 訪問控制框架

  • 計時器探針, 檢測函數的運行時間函數

  • 日誌記錄debug

在Elixir中, 主要是對函數進行裝飾. 一個函數裝飾器是形似@decorate 的一個符號註解, 緊接着函數定義的上一行, 它能夠用於給Elixir函數添加額外的功能. 函數裝飾器運行時開銷爲0, 由於它是在編譯時執行的.日誌

裝飾器以函數做爲參數, 而且返回一個通過修改, 或添加了新功能的函數. 它是一個高階函數.code

defmodule MyModule do
  use PrintDecorator

  @decorate print()
  def square(a) do
    a * a
  end
end

函數裝飾器其實是Elixir宏.對象

Elixir 中的函數裝飾器, 咱們用到了 decorator 這個庫.get

裝飾器的定義

定義裝飾器是比較簡單的, 建立一個模塊, 而且在模塊中 use Decorator.Define, [print: 0], 這樣就定義了一個名稱爲print的裝飾器了. string

下面是一個裝飾器的完整定義示例:

defmodule PrintDecorator do
  # 聲明裝飾器的名號, 參數數量
  use Decorator.Define, [print: 0]

  # 實現裝飾器
  def print(body, context) do
    quote do
      IO.puts("Function called: " <> Atom.to_string(unquote(context.name)))
      unquote(body)
    end
  end
end

裝飾器函數的參數(def print(...)) 爲函數體(AST, 抽象語法樹), 以及一個context參數, context 持有函數名稱, 定義模塊, 參數數量, 以及參數AST等信息.

編譯時傳參

裝飾器能夠進行編譯時參數傳遞, 例如日誌模塊僅打印消息級別爲:debug 的日誌.

@decorate print(:debug)
def foo() do
...

對此, 須要修改裝飾器模塊的定義:

defmodule PrintDecorator do
  use Decorator.Define, [print: 1]

  def print(level, body, context) do
  # ...
  end
end

裝飾器上下文

除了傳入裝飾器函數的函數體AST, 裝飾器函數還有一個傳入的上下文參數 context, context 參數包含了函數調用的相關信息:

def print(body, context) do
  Logger.debug("Function #{context.name}/#{context.arity} called in module #{context.module}!"
end

上下文具體包含哪些東西, 可經過 IO.puts #{inspect context} 打印出來.

下面是一個有用的示例, 是一個Phoenix框架中用戶檢查用戶認證的一個裝飾器宏.

defmodule Auth do
  def is_authorized(body, %{args: [conn, _params]}) do
    quote do
      if unquote(conn).assigns.user do
        unquote(body)
      else
        unquote(conn)
        |> send_resp(401, "unauthorized")
        |> halt()
      end
    end
  end
end

~完 (^_^!)

相關文章
相關標籤/搜索