A viselkedésekkel interfészeket (függvények és makrók halmazát) definiálhatunk egy viselkedésmodulban. Ezeket az interfészeket később különböző callback modulok valósíthatják meg. A közös interfésznek köszönhetően ezek a callback modulok felcserélhetően használhatók.
Figyeld meg: a „behaviours” szót brit helyesírással írjuk.
Egy viselkedés definiálásához létre kell hoznunk egy új modult, és meg kell adnunk azon függvények listáját, amelyek a kívánt interfész részét képezik. Minden függvényt a @callback modulattribútummal kell definiálni. A szintaxis ugyanaz, mint egy függvény típusspecifikáció esetében (@spec). Meg kell adnunk a függvény nevét, az argumentumtípusok listáját és az összes lehetséges visszatérési típust.
defmodule Countable do
@callback count(collection :: any) :: pos_integer
end
Ha egy létező viselkedést szeretnénk hozzáadni a modulunkhoz (azaz callback modult hozunk létre), a @behaviour modulattribútumot használjuk. Az értéke annak a viselkedésmodulnak a neve, amelyet hozzáadunk.
Ezután definiálnunk kell minden olyan függvényt (callbacket), amelyet az adott viselkedésmodul megkövetel. Ha valaki más viselkedését implementáljuk, például az Elixir beépített Access vagy GenServer viselkedését, a viselkedéshez tartozó összes callback listáját a hexdocs.pm dokumentációjában találjuk.
Egy callback modul nem korlátozódik arra, hogy csak a viselkedésének részét képező függvényeket implementálja. Egyetlen modul akár több viselkedést is implementálhat.
Ahhoz, hogy megjelöljük, melyik függvény melyik viselkedésből származik, minden függvény elé oda kell írnunk a @impl modulattribútumot. Az értéke pedig annak a viselkedésmodulnak a neve, amely ezt a callbacket definiálja.
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
Amikor viselkedést definiálunk, megadhatjuk egy callback alapértelmezett implementációját is. Ezt az implementációt a __using__/1 makró idézett kifejezésében kell megadni. Ahhoz, hogy a viselkedésmodul használói felülírhassák az alapértelmezett implementációt, a függvény implementációja után hívd meg a defoverridable/1 makrót. Ez egy kulcsszólistát vár, amelyben a kulcsok a függvénynevek, az értékek pedig a függvények aritásai.
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
Ne feledd, hogy a __using__/1 makrón belül függvényeket definiálni csak az alapértelmezett callback-implementációk megadása céljából ajánlott, más célra nem. A függvényeket viszont mindig definiálhatod egy másik modulban, és importálhatod őket a __using__/1 makróban.