[Elixir009]像GenServer一樣用behaviour來規範介面

来源:http://www.cnblogs.com/zhongwencool/archive/2016/04/06/elixir_behaviour.html
-Advertisement-
Play Games

1.Behaviour介紹 Erlang/Elixir的Behaviour類似於其它語言中的介面(interfaces),本質就是在指定behaviours的模塊中強制要求導出一些指定的函數,否則編譯時會warning。 其中Elixir中使用到behaviour的典範就是GenServer, Ge ...


1.Behaviour介紹

Erlang/Elixir的Behaviour類似於其它語言中的介面(interfaces),本質就是在指定behaviours的模塊中強制要求導出一些指定的函數,否則編譯時會warning。

其中Elixir中使用到behaviour的典範就是GenServer, GenEvent

曾經Elixir有一個叫Behaviour的模塊,但是在1.1時就已被deprecated掉了,現在你並不需要用一個Behaviour模塊才能定義一個behaviour啦。

讓我們一步步實現一個自定義的behaviour吧。

2. Warehouse和Warehouse.Apple測試用例

我們來定義一個倉庫,它只要進貨和出貨,它的狀態就是庫存量和類型,然後再在上一層封裝一個具體的apple倉庫,它基於倉庫,但它對外只顯示庫存量。

mix new behaviour_play
cd behaviour_play

先寫測試,搞明白我們希望的效果

# test/behaviour_play_test.exs
defmodule
BehaviourPlayTest do use ExUnit.Case doctest BehaviourPlay test
"warehouse working" do :ok = Warehouse.new(%{category: :fruit, store: 0}) assert Warehouse.query(:fruit) == %{category: :fruit, store: 0} :ok = Warehouse.increase(:fruit, 10) assert Warehouse.query(:fruit) == %{category: :fruit, store: 10} :ok = Warehouse.decrease(:fruit, 2) assert Warehouse.query(:fruit) == %{category: :fruit, store: 8} assert {:not_enough, 8} = Warehouse.decrease(:fruit, 9) end # 隱藏了是什麼類型的水果這個參數 test "add a apple warehouse" do :ok = Warehouse.Apple.new assert Warehouse.Apple.query == 0 :ok = Warehouse.Apple.increase(5) assert Warehouse.Apple.query == 5 assert {:not_enough, 5} == Warehouse.Apple.decrease(6) :ok = Warehouse.Apple.decrease(4) assert Warehouse.Apple.query == 1 end end

我們現在運行測試肯定是失敗的,因為我們還沒寫warehouse.ex,但是先寫測試是一個好習慣~

3. 構建Warehouse和Warehouse.Apple

# lib/warehouse.ex
defmodule Warehouse do  
  use GenServer
  def new(%{category: category, store: store} = init_state) when is_integer(store) and store >= 0 do
    {:ok, _pid} = GenServer.start(__MODULE__, init_state, name: category)
    :ok
  end
  def query(pid) do
    GenServer.call(pid, :query)
  end
  def increase(pid, num) when is_integer(num) and num > 0 do
    GenServer.call(pid, {:increase, num})
  end
  def decrease(pid, num)when is_integer(num) and num > 0 do
    GenServer.call(pid, {:decrease, num})
  end
  # GenServer callback
  def handle_call(:query, _from, state) do
    {:reply, state, state}
  end
  def handle_call({:increase, num}, _from, state) do
    {:reply, :ok, %{state| store: state.store + num}}
  end
  def handle_call({:decrease, num}, _from, state = %{store: store})when store >= num do
    {:reply, :ok, %{state| store: store - num}}
  end
  def handle_call({:decrease, _num}, _from, state) do
    {:reply, {:not_enough, state.store}, state}
  end
end

以上我們為把每一個warehouse都定義成新建立的一個GenServer進程。這時我們運行一下測試(mix test),會發現測試1已通過,但是我們具體指定到某一種類型apple的倉庫還沒有建出來。

# lib/apple.ex
defmodule
Warehouse.Apple do def new do Warehouse.new(
%{category: __MODULE__, store: 0}) end def query do state = Warehouse.query(__MODULE__) state.store end def increase(num) do Warehouse.increase(__MODULE__, num) end end

上面我們故意少定義了decrease/1這個函數,但是執行mix compile, 我們居然可以無任何warning的通過啦,我們只有到運行test時才能發現這個錯。

可是希望的結果是在編譯期間就能檢查出這種低級失誤來,而不是要到使用時才發現(如果我們沒有完備的test,就發現不了啦)

所以我們加入behaviour。

4.使用behaviour包裝Apple 

# lib/warehouse.ex的最前面加上@callback屬性
defmodule Warehouse do
  @callback new() :: :ok
  @callback query() :: number
  @callback increase(num :: number) :: :ok
  @callback decrease(num :: number) :: :ok| {:not_enough, number}
  #接原來的內容...

然後在apple倉庫中引入這個behaviour

# lib/apple.ex
defmodule Warehouse.Apple do
  @behaviour Warehouse
# 接原來的內容

這時再compile就可以看到對應的warning啦。

> mix compile
Compiled lib/warehouse.ex
lib/apple.ex:1: warning: undefined behaviour function decrease/1 (for behaviour Warehouse)

我們再把按指示把decrease/1補全就

# lib/apple.ex
def decrease(num) do
    Warehouse.decrease(__MODULE__, num)
  end

mix compile & mix test就全過啦。

6.幾個小細節

5.1 use GenServer後的callback並沒有doc,不會顯示在help裡面

我們use GenServer後,會自動生成GenServer對應的6個callback函數。但是當我們使用

iex(1)> h Warehouse. #按下tab自動補全
Apple  decrease/2    increase/2    new/1   query/1

並沒有看到這些callback函數的doc...

6.2 use GenServer後為什麼可以不必定義全部的callback.

可以在這裡看看use GenServer時發生了什麼,它先使用@behaviour GenServer, 希望定義這6個函數的預設行為,最後再把他們defoverridable

這就是為什麼我們在Warehouse裡面沒有定義init/1時它卻沒有warning的原因。這如果在Erlang中就是會warning的,因為他們沒有這麼flexible 的 Macro系統。

5.3 use實現的原理

可以參照看elixir的源代碼, 你需要在原模塊定義一個巨集__using__,所以我們的終極版本應該是

# lib/warehouse.ex 添加
defmacro __using__(options) do
    quote location: :keep do # 如果出錯,把錯誤的error trace打到本模塊來
      @behaviour Warehouse
      def new, do: Warehouse.new(%{category: unquote(options)[:category], store: 0})
      def query, do: Warehouse.query(unquote(options)[:category]).store
      def increase(num), do: Warehouse.increase(unquote(options)[:category], num)
      def decrease(num), do: Warehouse.decrease(unquote(options)[:category], num)
defoverridable [new: 0, query: 0, increase: 1, decrease: 1]
end end

然後把apple.ex裡面只需要use Warehouse一句話就搞定啦。

所以我們可以如果想定義很多的水果apple, banana,  pear,基本就是一句話的事~

#lib/apple.ex
defmodule Warehouse.Apple do
  use Warehouse, category: __MODULE__
end
#lib/banana.ex
defmodule Warehouse.Banana do
  use Warehouse, category: __MODULE__
end
# lib/pear.ex
defmodule Warehouse.Pear do
  use Warehouse, category: __MODULE__
end

 這樣的封裝就很簡化了很多代碼,是不是感覺寫elixir很爽呀~~~

 


show my Elixir apps around


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 早就聽說Python語言操作簡單,果然名不虛傳,短短幾句,就實現了基本的功能。 要檢測目標網站上是否存在指定的URL,其實過程很簡單: 1、獲得指定網站網頁的HTML代碼 2、在HTML代碼中查找指定的URL 3、如果存在,OK;否則,Error 整個程式引用了兩個lib庫,urllib2和sgml ...
  • 習慣更改(養成良好的編程習慣) 1.包含頭文件的方式,從C語言.h的方式改為<頭文件名>的方式 2.儘量使用迭代器代替下標操作3.建議:儘量避免使用指針和數組 ,儘可能使用vector和迭代器4.採用 string 類型取代 C 風格字元串(使用標準庫類型 string,除了增強安全性外,效率也提高 ...
  • 看到資料庫連接不由得想起了大一末參加團隊考核時的悲催經歷~~,還記得當初傻傻地按照書本的代碼打到 Eclipse 上,然後一運行就各種報錯。。。報錯後還傻傻地和書本的代碼一遍又一遍地進行核對,發現無誤後,還特別糾結——代碼和書本一樣,怎麼就報錯了呢? 最後通過 Google 才得知要添加驅動包,就這 ...
  • 在文件input.csv文件中,我們有數據如下 現在我們將input.csv文件下的讀取並寫入到output.csv文件,我們會用到fopen函數 函數原型:FILE * fopen(const char * path,const char * mode) fopen還有很多模式,比如 w,寫入文件 ...
  • 好久沒寫過雙緩存了,趁現在有空重新溫習下。 我們經常聽說雙緩存,但是很少使用多緩存,起碼大多數情況下是這樣吧。為什麼不需要多緩衝呢,今天分析下。並不是緩衝區越多越好,這個需要考慮具體的應用場景。我們抽象假設一下應用場景,為了簡化場景,假設只有一個讀線程和一個寫線程,設讀時間為rt,寫時間為wt,有三 ...
  • 易偉微信公眾平臺介面傻瓜教程部分內容:微信介面9超鏈接.rmvb微信介面8音樂信息.rmvb微信介面7圖文信息.rmvb微信介面6關註回覆.rmvb微信介面5關鍵詞回覆.rmvb微信介面50連闖三關.rmvb微信介面4介面驗證.rmvb微信介面49簡答題.rmvb微信介面48正則表達式.rmvb微信 ...
  • 早就聽說了dubbo的好處,但是在項目中一直沒有使用的機會,所以一直不知道怎麼使用。今天晚上有空,簡單的學習一下 就當入個門,以後項目中遇到的話,那麼使用起來就比較簡單了,至於介紹的話,我就不總結了,其實就是很好的解決了分散式 管理的問題,並且操作起來非常的方便。 下麵這張圖是從官網上截圖的 節點角 ...
  • 協同過濾常常被用於分辨某位特定顧客可能感興趣的東西,這些結論來自於對其他相似顧客對哪些產品感興趣的分析。協同過濾以其出色的速度和健壯性,在全球互聯網領域炙手可熱。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...