让我们说elixir库定义:
defmodule Decoder do
def decode(%{"BOOL" => true}), do: true
def decode(%{"BOOL" => false}), do: false
def decode(%{"BOOL" => "true"}), do: true
def decode(%{"BOOL" => "false"}), do: false
def decode(%{"B" => value}), do: value
def decode(%{"S" => value}), do: value
def decode(%{"M" => value}), do: value |> decode
def decode(item = %{}) do
item |> Enum.reduce(%{}, fn({k, v}, map) ->
Map.put(map, k, decode(v))
end)
end
end
我想定义一个模块MyDecoder
,它只会在上面的模块中添加一个def decode
。在oo语言中,这将通过某种继承/ mixin / extends来完成。
我如何在灵药中做到这一点?
答案 0 :(得分:16)
有一种机制可以扩展模块的行为。它被称为协议。您可以找到更多信息here。您可以将Elixir协议视为类似于OO中的接口。
但是,在这种特殊情况下,它就像是用大锤拍打苍蝇。我的意思是你可能会重写代码以使用协议但是如果你想简单地扩展解析器然后分叉代码并进行修改。哦,不要忘记将PR发送回原始开发人员,因为他可能也希望得到您的修复。
有时最简单的答案是最好的答案。即使这是OO代码,如果某些开发人员继承了类或类似的东西,我会在代码审查中标记它。为什么?因为遗传会导致病态code coupling。
一般来说FP(注意我在这里做了很大的概括)我们通常扩展行为的方式是通过高阶函数。也就是说,如果我们想要不同的行为,我们就不会使用多态性;我们只是直接将我们想要的行为传递给更高阶的函数。当我说“传递行为”时,我的意思是什么?考虑我有一些验证码,例如:
defmodule V do
def is_odd?(v) do
rem(v,2) != 0
end
end
defmodule T do
def is_valid_value?(v, f) do
if f(v), do: true, else: false
end
end
在其他地方我会T.is_valid_value?(myvalue, V.is_odd?)
。突然间,我的客户意识到,不是检查价值是否奇怪,而是需要检查它是否大于100.所以我会沿着这些方向做点什么:
defmodule V do
def greater_than_100?(v) do
v > 100
end
end
然后我会改变对此的调用:T.is_valid_value?(myvalue, V.greater_than_100?)
注意:我故意保持代码非常简单,以说明问题。这可能不是有效的语法。我没有检查过,现在我不能。
就是这样。就这样。智能开发人员可以不同意,但对我而言,这比继承行为和覆盖行为更直接,更容易理解。
答案 1 :(得分:5)
显然,你可以。看看this gist,它使用一些相当“模糊”的方法来列出模块的公共函数,然后从中生成委托。这很酷。
以下是它的全部内容:
defmodule Extension do
defmacro extends(module) do
module = Macro.expand(module, __CALLER__)
functions = module.__info__(:functions)
signatures = Enum.map functions, fn { name, arity } ->
args = if arity == 0 do
[]
else
Enum.map 1 .. arity, fn(i) ->
{ binary_to_atom(<< ?x, ?A + i - 1 >>), [], nil }
end
end
{ name, [], args }
end
quote do
defdelegate unquote(signatures), to: unquote(module)
defoverridable unquote(functions)
end
end
end
您可以像这样使用它:
defmodule MyModule do
require Extension
Extension.extends ParentModule
# ...
end
不幸的是,它会对最近的Elixir版本发出警告,但我确信这可以解决。除此之外,它就像一个魅力!
答案 2 :(得分:1)
如果您不控制原始模块,我不确定是否有直接的解决方案。也许您可以尝试递归地预处理数据,然后将结果提供给原始实现。
如果你可以控制原始模块,一种方法是将公共子句提取到宏中,然后在实际的解码器模块中使用它:
defmodule Decoder.Common do
defmacro __using__(_) do
quote do
def decode(%{"BOOL" => true}), do: true
def decode(%{"BOOL" => false}), do: false
def decode(%{"BOOL" => "true"}), do: true
def decode(%{"BOOL" => "false"}), do: false
def decode(%{"B" => value}), do: value
def decode(%{"S" => value}), do: value
def decode(%{"M" => value}), do: value |> decode
def decode(item = %{}) do
item |> Enum.reduce(%{}, fn({k, v}, map) ->
Map.put(map, k, decode(v))
end)
end
end
end
end
defmodule Decoder do
use Decoder.Common
end
defmodule MyDecoder do
def decode(%{"FOO" => value}), do: "BAR"
use Decoder.Common
end
答案 3 :(得分:1)
也许defdelegate
可以解决问题:
defmodule MyDecoder do
def decode(%{"X" => value}), do: value
defdelegate decode(map), to: Decoder
end