Elixir 从零 · 第 21 课(展开目录)
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 2140 分钟

同一句话,不同做法

用 protocol 为不同数据类型定义同一项能力。

01 · 先看完整例子

让 list 与 map 都会报大小

同一个函数名,根据第一个参数类型选择实现。

Elixir
defprotocol Measure do
  @spec size(t()) :: non_neg_integer()
  def size(value)
end

# list 使用 length/1
defimpl Measure, for: List do
  def size(value), do: length(value)
end

# map 使用 map_size/1
defimpl Measure, for: Map do
  def size(value), do: map_size(value)
end

{Measure.size([:a, :b]), Measure.size(%{a: 1})}
先对照结果
  • 得到 {2, 1}。
02 · 回头拆代码

从第一行往下读

  1. protocol 只声明 size/1 应存在,不决定算法。
  2. List 与 Map 实现各自最自然的计数方法。
  3. 未实现类型调用时会得到 Protocol.UndefinedError。
03 · 认清新符号

符号不是暗号

defprotocol

声明一项跨类型能力。

defimpl ... for:

为指定类型实现该能力。

Measure.size(value)

调用时由值的类型选择实现。

04 · 代码里的概念

把名字和意思对上

01

protocol

根据第一个参数的数据类型选择实现的多态约定。

02

实现

某种类型怎样完成 protocol 声明的函数。

05 · 再说清楚

这些写法为什么有用

有时问题不该问“你属于哪个模块”,而该问“你会不会做这件事”。protocol 就是能力约定。

defprotocol 声明函数,defimpl 为具体类型提供实现。调用者只面对同一个名字。

protocol 适合由数据类型决定行为。若只是项目内部替换模块,behaviour 往往更合适。

06 · 自己改一次

合上答案,动手

为 BitString 加实现,使用 String.length/1。

练习起点
defimpl Measure, for: ____ do
  def size(value), do: ____
end

目标结果: Measure.size("长安") 得到 2。

卡住了,再看提示

Elixir 字符串属于 BitString;正文调用 String.length(value)。

运行过以后,再看一种答案
一种答案
defimpl Measure, for: BitString do
  # 按可见字符计算字符串大小
  def size(value), do: String.length(value)
end
想一想:protocol 主要根据哪个信息选择实现?

根据第一个参数的数据类型选择实现。

带走

记住三句

  1. 1

    protocol 描述数据具备的能力。

  2. 2

    每种类型可以有自己的实现。

  3. 3

    调用端保持同一个函数入口。

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