官术网_书友最值得收藏!

Behaviours

Behaviours provide a way to describe a set of functions that have to be implemented by a module, while also ensuring that the module implements the functions in that set. If you come from an object-oriented programming background, you can think of behaviours as abstract base classes that define interfaces. After declaring the behaviour, we can then create other modules that adopt this behaviour. A behaviour creates an explicit contract, which states what the modules that adopt the behaviour need to implement. This way, we can have our business logic tied to abstractions (the behaviour) instead of concrete implementations. We can swap two implementations of the same behaviour with very little and localized change in our applicationthe place where we define the implementation of the behaviour we're going to use.

Let's demonstrate this concept with an example. We'll define a behaviour, called Presenter, that has only one function: present. We'll then define a module that adopts this behaviour, called CLIPresenter. We could also have other modules that would adopt this behaviour, such as a GraphicalPresenter. A behaviour is created using the @callback directive inside a module, providing a typespec for each function this behaviour contains. Thus, to define our Presenter behaviour, we use the following:

$ cat examples/presenter.ex
defmodule Presenter do
@callback present(String.t) :: atom
end

And now we define the module that will adopt this behaviour:

$ cat examples/cli_presenter.ex
defmodule CLIPresenter do
@behaviour Presenter

@impl true
def present(text) do
IO.puts(text)
end
end

We can see this module working in the next snippet:

iex> CLIPresenter.present("printing text to the command line")
printing text to the command line
:ok

From Elixir 1.5 onwards, we may use the @impl directive to mark the functions that are implemented as callbacks for a behaviour. In our case, we'd put the @impl directive on top of the present function:

$ cat examples/cli_presenter_with_impl_annotation.ex
defmodule CLIPresenter do
@behaviour Presenter

@impl true
def present(text) do
IO.puts(text)
end
end

We can be even more specific and use @impl Presenter, to state that this function is a callback from the Presenter behaviour. This brings two major advantages:

  • Increased readability, as it's now explicit which functions make up part of our API and which functions are callback implementations.
  • Greater consistency, as the Elixir compiler will check whether the functions you're marking with @impl are part of a behaviour your module is adopting.

Note that when you set @impl on a module, you must set it on all callback functions on that module, otherwise a warning will be issued. Furthermore, you can only use @impl on functions that are callbacks, otherwise a compilation warning will be issued as well.

You can use @optional_callbacks to mark one or more functions as optional when adopting a certain behaviour. You need to provide a keyword list, with function names as keys, and arity as the value. If we wanted our present/1 function to be optional in the Presenter behaviour, we would use:

$ cat examples/presenter_with_optional_callbacks.ex
defmodule Presenter do
  @callback present(String.t) :: atom
  @optional_callbacks present: 1
end

If we didn't implement all of the functions declared in the Presenter behaviour (by commenting the CLIPresenter.present/1 function, for instance), the Elixir compiler would emit the following warning:

warning: undefined behaviour function present/1 (for behaviour Presenter)

Behaviours help us follow the open/closed principle, since we can extend our system without modifying what we already have. If, in the future, we would need a new type of presenter, we'd just have to create a new module that adopts the Presenter behaviour, without having to change the behaviour or its current implementations. We'll be using behaviours in the application we'll build in this book, and you'll see them in action again in the next chapter.

主站蜘蛛池模板: 黄骅市| 淮南市| 昌黎县| 中卫市| 绥滨县| 河曲县| 两当县| 英德市| 南昌市| 古交市| 图木舒克市| 凤城市| 丹凤县| 兴安盟| 正阳县| 南皮县| 中超| 池州市| 湖南省| 佛坪县| 寿宁县| 南溪县| 称多县| 方正县| 南乐县| 宜川县| 哈巴河县| 徐汇区| 海阳市| 泸溪县| 皋兰县| 芦溪县| 丰城市| 通河县| 东乌珠穆沁旗| 龙门县| 封开县| 盱眙县| 仪征市| 麦盖提县| 扶绥县|