Clojure Protocol「继承」:强制实现父协议方法的方案探讨
解决协议继承中的命名空间遮蔽问题
我刚好碰到过类似的问题,结合你提到的命名空间结构(看起来是Elixir场景),不用自定义协议/扩展宏的话,有几个实用的方案可以解决这个问题:
1. 显式使用完整命名空间调用协议方法
避免import多个协议命名空间导致的遮蔽,直接在客户端代码里用完整的命名空间路径调用协议方法。比如:
# 不要这样做(会导致遮蔽) import c.a.A import c.b.B # 而是直接用完整路径 c.a.A.my_method(some_instance) c.b.B.my_method(some_instance)
优点:最直接,完全避免命名冲突,不需要修改协议定义;
缺点:代码会稍微冗长,但可读性更强,明确知道调用的是哪个协议的方法。
2. 在协议B中显式转发协议A的方法
在协议B的定义里,把协议A的所有方法都显式地转发到A的实现,同时要求实现B的类型必须实现这些方法(或者依赖已有的A实现)。比如:
# 定义协议B defmodule c.b.B do @callback my_method(arg :: any()) :: any() # 转发到A的方法,确保实现B的类型必须兼容A def my_method(x), do: c.a.A.my_method(x) end # 实现B的类型必须同时实现A defimpl c.b.B, for: MyType do def my_method(x), do: MyType.a_compatible_implementation(x) end
这样一来,协议B的方法在c.b命名空间下,不会和c.a的A方法冲突;同时实现B的类型必须提供符合A要求的实现(要么直接实现A,要么在B的实现里兼容A的方法签名)。
优点:客户端代码可以正常import c.b.B,不用写长路径;
缺点:如果协议A的方法有变更,需要手动更新协议B的转发逻辑。
3. 为协议B设置默认实现,委托给协议A
利用协议的默认实现特性,让协议B的默认实现直接调用协议A的方法,同时强制要求实现B的类型必须先实现A:
defmodule c.b.B do @callback my_method(arg :: any()) :: any() @fallback_to_any true end defimpl c.b.B, for: Any do def my_method(x) do # 确保x的类型已经实现了A,否则会抛出协议未实现的错误 c.a.A.my_method(x) end end
这样,任何实现B的类型如果没有自己实现B的方法,会自动调用A的实现,但前提是该类型已经实现了A——如果没实现,会直接报错,间接保证了实现B的类型必须兼容A的所有方法。
优点:不需要重复定义A的方法签名在B里,自动复用A的实现;
缺点:依赖@fallback_to_any,如果有类型需要自定义B的方法,还是要单独实现。
这些方案都不需要自定义宏,完全利用语言本身的特性就能解决命名空间遮蔽和协议继承的要求。
内容的提问来源于stack exchange,提问作者jennykwan
相关产品推荐
相关产品推荐

