Java 模式里面有一个概念叫「对修改关闭, 对扩展开放」, 这是一个面向对象的组件可重用原则. 装饰器就是实现该原则的一个经典实例.
装饰器原理
通过符号注解的方式, 给被注解的函数或对象添加新功能, 重写现有的功能, 而又不对现有的代码做变化的一种方法. 它对使用者是透明的. 通过装饰器可以实现的常用功能包括:
访问控制
计时器探针, 检测函数的运行时间
日志记录
在Elixir中, 主要是对函数进行装饰. 一个函数装饰器是形似@decorate
的一个符号注解, 紧接着函数定义的上一行, 它可以用于给Elixir函数添加额外的功能. 函数装饰器运行时开销为0, 因为它是在编译时执行的.
装饰器以函数作为参数, 并且返回一个经过修改, 或添加了新功能的函数. 它是一个高阶函数.
defmodule MyModule do
use PrintDecorator
@decorate print()
def square(a) do
a * a
end
end
函数装饰器实际上是Elixir宏.
Elixir 中的函数装饰器, 我们用到了 decorator 这个库.
装饰器的定义
定义装饰器是比较简单的, 创建一个模块, 并且在模块中 use Decorator.Define, [print: 0]
, 这样就定义了一个名称为print
的装饰器了.
下面是一个装饰器的完整定义示例:
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
~完 (^_^!)