协议

协议 属于 Elixir

4 个练习

关于 协议

协议具有以下特点:

  • 一种实现多态的机制。
  • 用 defprotocol 定义。
  • 用 defimpl 为特定的数据类型实现。

协议的一大优势在于:为某个特定数据类型实现协议时,不必把它和数据类型的定义或协议的定义捆绑在一起。这样就能编写出易于扩展的库:用户无需修改库本身,就能让它支持自己定义的数据类型。

实现

可以为 Elixir 的所有类型实现协议:Tuple、Atom、List、BitString、Integer、Float、Function、PID、Map、Port、Reference、Any,以及结构体。

可以同时为多个类型实现协议:

defimpl Reversible, for: [Map, List] do
  def reverse(term) do
    Enum.reverse(term)
  end
end

在定义结构体的模块内部直接为该结构体实现协议时,可以省略 :for 参数:

defmodule ShoppingCart do
  defstruct [:items]

  defimpl Reversible do
    def reverse(shopping_cart) do
      Enum.reverse(shopping_cart)
    end
  end
end

回退到 Any

默认情况下,为某个数据类型调用一个尚未针对它实现的协议,会抛出 Protocol.UndefinedError 错误:

to_string({})
# => ** (Protocol.UndefinedError) protocol String.Chars not implemented for {} of type Tuple
#        (elixir 1.10.3) lib/string/chars.ex:3: String.Chars.impl_for!/1
#        (elixir 1.10.3) lib/string/chars.ex:22: String.Chars.to_string/1

可以提供一个回退实现来避免这种情况,遇到上述情形时就会使用它。为此,在定义协议时把 @fallback_to_any 模块属性 设为 true,并为 Any 类型写一个实现:

defprotocol Reversible do
  @fallback_to_any true
  def reverse(term)
end

defimpl Reversible, for: Any do
  def reverse(term) do
    term
  end
end

注意,大多数情况下抛出错误是更可取的做法,因为很难为_所有_数据类型对某个协议做出合理的假设。

值得注意的内置协议

  • Enumerable:Enum 和 Stream 模块中的函数可以处理实现了该协议的数据类型。
  • Collectable:函数 Enum.into/2 使用该协议把一个可枚举对象插入到集合中。
  • Inspect:你可以实现这个协议,来改变 Kernel.inspect/2 把某个数据类型表示为字符串的方式。
  • String.Chars:你可以实现这个协议,来改变 Kernel.to_string/1 把某个数据类型表示为字符串的方式。
通过 GitHub 编辑 该链接会在新窗口或标签页中打开

学习 协议

练习已锁定

再解锁 1 个练习即可练习 协议