Logo
Section 1 / 3

行为

简要了解 Elixir 中的行为(behaviours)。

导入

行为

简要了解 Elixir 中的行为(behaviours)。

当我们编写 OTP 服务器时,我们创建了一个以如下代码开头的模块:

1defmodule Sequence.Server do 2 use GenServer 3 ...

在本章中,我们将探讨像 use GenServer 这样的语句实际上做了什么,以及如何编写模块,使其能够扩展使用它们的其他模块的功能。

Elixir 中的 行为(behaviour) 本质上只是一组函数的集合。一个声明实现了特定行为的模块,必须实现该行为所定义的所有函数。如果未实现,Elixir 会在编译时生成警告。我们可以将行为定义看作类似于某些面向对象语言中的抽象基类。

因此,行为有点像 Java 中的接口。模块通过它来声明自己实现了某个特定接口。例如,OTP 的 GenServer 必须实现一组标准的回调函数(如 handle_call、handle_cast 等)。通过声明我们的模块实现了这一行为,我们让编译器验证我们确实提供了必要的接口。这降低了出现意外运行时错误的可能性。

定义行为

我们使用 @callback 来定义行为。

例如,mix 工具可以从多种源码控制系统中获取依赖项。默认情况下,它支持 Git 和本地文件系统。然而,源码控制系统(mix 内部简称为 SCM)的接口是通过行为定义的,这使得可以干净地添加新的版本控制系统。该行为定义在模块 Mix.SCM 中:

1defmodule Mix.SCM do 2 @moduledoc """ 3 该模块提供辅助函数,并定义了 Mix 使用的任何 SCM 所需的行为。 4 """ 5 @type opts :: Keyword.t 6 @doc """ 7 返回一个布尔值,表示依赖是否可获取;或者表示该依赖应已存在于文件系统中。 8 本地依赖(即不可获取的依赖)会在父项目每次编译时自动重新编译。 9 """ 10 @callback fetchable? :: boolean 11 @doc """ 12 返回表示 SCM 的字符串。此字符串用于打印依赖信息,不用于检查, 13 因此信息应简洁明了,易于识别。 14 """ 15 @callback format(opts) :: String.t 16 # 以及其他 8 个回调函数 17end

该模块定义了实现该行为的模块必须支持的接口。它使用 @callback 来定义行为中的函数。但语法看起来略有不同,这是因为我们在使用 Erlang 类型规范的微型语言。例如,fetchable? 函数不接受参数,返回布尔值;format 函数接收一个类型为 opts 的参数,该类型在代码顶部被定义为关键词列表,并返回字符串。

除了类型规范外,我们还可以在行为定义中包含模块级和函数级的文档说明。

声明行为

现在我们已经定义了行为,就可以使用 @behaviour 属性声明另一个模块实现该行为。以下是 mix 中 Git 实现的开始部分:

1defmodule Mix.SCM.Git do 2 @behaviour Mix.SCM 3 def fetchable? do 4 true 5 end 6 def format(opts) do 7 opts[:git] 8 end 9 # ... 10end

该模块实现了 Mix.SCM 中声明的所有回调函数。这个模块可以顺利编译。但假设我们拼错了 fetchable:

1defmodule Mix.SCM.Git do 2 @behaviour Mix.SCM 3 def fetchible? do 4 true 5 end 6 def format(opts) do 7 opts[:git] 8 end 9 # ... 10end

当我们编译该模块时,会得到如下警告:

1git.ex:1: warning: undefined behaviour function fetchable?/0 (for behaviour Mix.SCM)

行为为我们提供了一种既文档化又强制要求模块实现其公共函数的方式。

更进一步

在 Mix.SCM 的 Git 实现中,我们创建了一系列实现该行为的函数。但这些函数很可能不是该模块中唯一的函数。而且,除非我们对 Mix.SCM 行为非常熟悉,否则很难区分哪些是回调函数,哪些是普通函数。

为了解决这个问题,我们可以使用 @impl 属性标记回调函数。该属性接受 true 或行为名称作为参数。

1defmodule Mix.SCM.Git do 2 @behaviour Mix.SCM 3 def init(arg) do # 普通函数 4 # ... 5 end 6 @impl Mix.SCM 7 def fetchable? do # 回调函数 8 true 9 end 10 @impl Mix.SCM 11 def format(opts) do # 回调函数 12 opts[:git] 13 end

宏与代码求值

use 和 __using__