Elixir 从零 · 第 22 课(展开目录)
01 · 先让代码说话02 · 先认六种值03 · 把值装起来04 · 从旧 list 生成新 list05 · 让形状对上06 · 用 case 做选择07 · 把文字安全变成整数08 · 拆开 &1 和管道09 · 把代码放进项目10 · 真假不只靠 true11 · 走进嵌套数据12 · 一字不一定一字节13 · 让函数按形状接活14 · 把一列数收成一个值15 · 给不同问题选不同路16 · 给 map 一张名片17 · 让项目自己验答案18 · 让成功和失败长得一样19 · 读写文件先管好路20 · 只算眼前需要的21 · 同一句话,不同做法22 · 先约好模块会做什么23 · 把数据形状写在门口24 · 测试不只看一条好路25 · 把项目磨成一个命令26 · 定下小项目边界27 · 把真实文字洗干净28 · 只开三扇门29 · 把约定钉在代码旁30 · 打包,也留下脚印31 · 照任务书交卷
ELIXIR · 进阶 · LESSON 2240 分钟

先约好模块会做什么

用 behaviour 和 callback 约定模块接口,再替换具体实现。

01 · 先看完整例子

约定一个问候接口

Welcome 只依赖会 hello/1 的模块。

Elixir
defmodule Greeter do
  @callback hello(String.t()) :: String.t()
end

defmodule ChineseGreeter do
  @behaviour Greeter

  # 实现约定的 hello/1
  @impl true
  def hello(name), do: "你好,#{name}"
end

defmodule Welcome do
  def run(greeter, name), do: greeter.hello(name)
end

Welcome.run(ChineseGreeter, "小满")
先对照结果
  • 得到 "你好,小满"。
02 · 回头拆代码

从第一行往下读

  1. Greeter 定义合同,不负责具体语言。
  2. ChineseGreeter 实现合同,并用 @impl true 标清函数来源。
  3. 调用者接收模块参数,因此可以换另一种实现。
03 · 认清新符号

符号不是暗号

@callback

声明必须实现的函数及类型。

@behaviour Module

声明当前模块遵守某个 behaviour。

module.function

通过传入模块调用可替换实现。

04 · 代码里的概念

把名字和意思对上

01

behaviour

一组模块级函数约定。实现模块应提供全部 callback。

02

callback

behaviour 要求实现模块提供的函数签名。

05 · 再说清楚

这些写法为什么有用

protocol 按数据类型选实现;behaviour 约束模块必须提供哪些函数。两者解决不同问题。

@callback 写出函数名、参数和返回类型。实现模块用 @behaviour 声明自己遵守约定。

behaviour 常用于可替换边界:存储、通知、支付。核心代码依赖约定,不依赖某一家实现。

设计选择

为什么这样写

为什么 behaviour 只规定回调,不直接提供进程?

这次选择

behaviour 先约定模块必须实现哪些函数。GenServer/gen_server 是带运行时协议的 behaviour,但一般 behaviour 本身不会启动任何东西。

代价与边界

它统一边界,却不替你决定状态、监督、容量和错误策略。

换一把尺子

从 Java、Python、JavaScript 看过来

Java

熟悉的起点
interface 定义方法契约。
BEAM 在意什么
behaviour 约束 module/function/arity;调用者仍调用模块函数。
别带错直觉
实现 behaviour 不等于创建对象或线程。

Python

熟悉的起点
ABC 或 Protocol。
BEAM 在意什么
callback 元数据让工具检查实现,也能被 OTP 框架调用。
别带错直觉
它不是 Python 装饰器式的运行时包装。

JavaScript

熟悉的起点
约定对象形状或 TypeScript interface。
BEAM 在意什么
behaviour 存在于 BEAM 模块与 callback 入口层。
别带错直觉
类型约定和进程生命周期仍是两件事。
06 · 自己改一次

合上答案,动手

增加 QuietGreeter,始终返回 "你好"。

练习起点
defmodule QuietGreeter do
  @behaviour ____
  @impl true
  def hello(_name), do: ____
end

目标结果: Welcome.run(QuietGreeter, "阿青") 得到 "你好"。

卡住了,再看提示

behaviour 是 Greeter,返回固定字符串。

运行过以后,再看一种答案
一种答案
defmodule QuietGreeter do
  @behaviour Greeter
  @impl true
  # 这个实现选择忽略名字
  def hello(_name), do: "你好"
end
想一想:behaviour 会自动替你写 callback 的正文吗?

不会。它只声明约定并帮助编译器检查;正文仍由实现模块完成。

带走

记住三句

  1. 1

    behaviour 约束模块接口。

  2. 2

    callback 写清函数与返回类型。

  3. 3

    依赖约定能让边界实现更容易替换。

本课结束代码也跑过一次,就把这一课收好。