Behaviours 允许我们在一个 _behaviour 模块_中定义接口(一组函数和宏),这些接口之后可以由不同的_回调模块_实现。得益于共享的接口,这些回调模块可以互换使用。
注意“behaviours”的英式拼写。
要定义 behaviour,我们需要创建一个新模块,并指定属于所需接口的函数列表。每个函数都需要使用 @callback 模块属性来定义。语法与函数类型规格(@spec)完全相同。我们需要指定函数名、实参类型列表以及所有可能的返回类型。
defmodule Countable do
@callback count(collection :: any) :: pos_integer
end
要向我们的模块添加已有的 behaviour(创建回调模块),我们使用 @behaviour 模块属性。它的值应该是我们要添加的 behaviour 模块的名称。
然后,我们需要定义该 behaviour 模块要求的所有函数(回调)。如果我们要实现别人的 behaviour,比如 Elixir 内置的 Access 或 GenServer behaviour,可以在 hexdocs.pm 的文档中找到该 behaviour 所有回调的列表。
回调模块并不局限于只实现其 behaviour 中的函数。单个模块也可以实现多个 behaviour。
要标明某个函数来自哪个 behaviour,我们应在每个函数前使用模块属性 @impl。它的值应该是定义该回调的 behaviour 模块的名称。
defmodule BookCollection do
@behaviour Countable
defstruct [:list, :owner]
@impl Countable
def count(collection) do
Enum.count(collection.list)
end
def mark_as_read(collection, book) do
# other function unrelated to the Countable behaviour
end
end
定义 behaviour 时,可以为某个回调提供默认实现。这个实现应定义在 __using__/1 宏的 quoted 表达式中。为了让 behaviour 模块的使用者能够覆盖默认实现,请在函数实现之后调用 defoverridable/1 宏。它接受一个关键字列表,其中函数名作为键,函数元数作为值。
defmodule Countable do
@callback count(collection :: any) :: pos_integer
defmacro __using__(_) do
quote do
@behaviour Countable
def count(collection), do: Enum.count(collection)
defoverridable count: 1
end
end
end
请注意,除了定义默认回调实现之外,不建议在 __using__/1 中定义函数,但你始终可以在另一个模块中定义函数,并在 __using__/1 宏中导入它们。