Behaviour 讓我們可以在一個 _behaviour 模組_中定義介面(一組函式與巨集),之後再由不同的 _callback 模組_實作。有了這個共用的介面,那些 callback 模組就能互換使用。
注意「behaviours」的英式拼法。
要定義一個 behaviour,我們需要建立一個新模組,並列出屬於這個介面的函式清單。每個函式都必須用 @callback 模組屬性來定義。語法和函式 typespec(@spec)完全相同。我們需要指定函式名稱、引數型別清單,以及所有可能的回傳型別。
defmodule Countable do
@callback count(collection :: any) :: pos_integer
end
要為我們的模組加上一個現有的 behaviour(建立一個 callback 模組),我們使用 @behaviour 模組屬性。它的值應該是我們要加入的 behaviour 模組名稱。
接著,我們需要定義該 behaviour 模組要求的所有函式(callback)。如果我們要實作別人的 behaviour,例如 Elixir 內建的 Access 或 GenServer behaviour,可以在 hexdocs.pm 的文件裡找到該 behaviour 所有 callback 的清單。
一個 callback 模組不只能實作屬於它 behaviour 的那些函式。單一模組也可以實作多個 behaviour。
為了標示哪個函式來自哪個 behaviour,我們應該在每個函式前面使用 @impl 模組屬性。它的值應該是定義這個 callback 的 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 時,可以為某個 callback 提供預設實作。這個實作應該定義在 __using__/1 巨集的 quoted expression 裡。為了讓 behaviour 模組的使用者能覆寫預設實作,請在函式實作之後呼叫 defoverridable/1 巨集。它接受一個關鍵字清單,以函式名稱為鍵、函式 arity 為值。
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
請注意,除了定義預設的 callback 實作之外,不建議在 __using__/1 裡定義函式;不過你隨時可以在另一個模組中定義函式,再將它們匯入 __using__/1 巨集。