(defprotocol AProtocol
"A doc string for AProtocol abstraction"
(bar [a b] "bar docs")
(baz [a] [a b] [a b c] "baz docs"))
Clojure 是用抽象来编写的。有用于序列、集合、可调用性等的抽象。此外,Clojure 还提供了这些抽象的许多实现。抽象由宿主接口指定,实现由宿主类指定。虽然这足以启动语言,但它让 Clojure 缺乏类似的抽象和低级实现设施。 协议 和 数据类型 功能添加了强大的灵活机制,用于抽象和数据结构定义,并且在与宿主平台的设施相比没有折衷。
协议有几个动机
提供高性能、动态多态性构造,作为接口的替代方案
支持接口的最佳部分
仅指定,不实现
单一类型可以实现多个协议
同时避免一些缺点
实现哪些接口是类型作者的设计时选择,无法在以后扩展(尽管接口注入可能最终会解决这个问题)
实现接口会创建 isa/instanceof 类型关系和层次结构
通过允许不同方独立扩展类型集、协议和类型上的协议实现来避免“表达式问题”
无需包装器/适配器
支持多方法的 90% 案例(对类型的单一调度),同时提供更高层次的抽象/组织
协议是在 Clojure 1.2 中引入的。 |
协议是用 defprotocol 定义的一组命名方法和它们的签名。
(defprotocol AProtocol
"A doc string for AProtocol abstraction"
(bar [a b] "bar docs")
(baz [a] [a b] [a b c] "baz docs"))
没有提供实现
可以为协议和函数指定文档
以上产生一组多态函数和一个协议对象
所有都由包含定义的命名空间限定
生成的函数根据其第一个参数的类型进行调度,因此必须至少有一个参数
defprotocol 是动态的,不需要 AOT 编译
defprotocol 会自动生成一个相应的接口,其名称与协议相同,例如,给定一个协议 my.ns/Protocol,一个接口 my.ns.Protocol。该接口将具有与协议函数相对应的函数,并且协议将自动与接口的实例一起使用。
(defprotocol P
(foo [x])
(bar-me [x] [x y]))
(deftype Foo [a b c]
P
(foo [x] a)
(bar-me [x] b)
(bar-me [x y] (+ c y)))
(bar-me (Foo. 1 2 3) 42)
= > 45
(foo
(let [x 42]
(reify P
(foo [this] 17)
(bar-me [this] x)
(bar-me [this y] x))))
> 17
希望参与协议的 Java 客户端可以通过实现协议生成的接口最有效地做到这一点。
协议的外部实现(当您希望不在您控制范围内的类或类型参与协议时需要)可以使用 extend 结构提供
(extend AType
AProtocol
{:foo an-existing-fn
:bar (fn [a b] ...)
:baz (fn ([a]...) ([a b] ...)...)}
BProtocol
{...}
...)
extend 采用类型/类(或接口,见下文)、一个或多个协议 + 函数映射(已评估)对。
将扩展协议方法的多态性,以便在提供 AType 作为第一个参数时调用提供的函数
函数映射是将关键字化的函数名称映射到普通 fn 的映射
这便于轻松重用现有的 fn 和映射,用于代码重用/混合,无需派生或组合
您可以在接口上实现协议
这主要是为了便于与主机(例如 Java)进行互操作
但也打开了实现的偶然多重继承的大门
因为一个类可以继承自多个接口,而这两个接口都实现了该协议
如果一个接口是从另一个接口派生的,则使用派生的更多接口,否则使用哪个接口是不确定的。
实现的 fn 可以假定第一个参数是 AType 的实例
您可以在 nil 上实现协议
要定义协议的默认实现(针对除 nil 之外的其他情况),只需使用 Object
协议是完全具象化的,并通过 extends? 、 extenders 和 satisfies? 支持反射功能。
请注意便捷宏 extend-type 和 extend-protocol
如果您要提供内联的外部定义,与直接使用 extend 相比,这些将更加方便
(extend-type MyType
Countable
(cnt [c] ...)
Foo
(bar [x y] ...)
(baz ([x] ...) ([x y zs] ...)))
;expands into:
(extend MyType
Countable
{:cnt (fn [c] ...)}
Foo
{:baz (fn ([x] ...) ([x y zs] ...))
:bar (fn [x y] ...)})
协议是一个开放系统,可以扩展到任何类型。为了最大程度地减少冲突,请考虑以下指南
如果您不拥有协议或目标类型,则只应在应用程序(而不是公共库)代码中进行扩展,并预计可能被任一所有者破坏。
如果您拥有协议,则可以为常用目标提供一些基本版本作为包的一部分,但要遵守这样做的独裁性质。
如果您正在发布潜在目标的库,您可以为它们提供常用协议的实现,但要遵守您正在指定的事实。在扩展 Clojure 自身包含的协议时,您应该特别注意。
如果您是库开发人员,则如果既不拥有协议也不拥有目标,则不应进行扩展
另请参阅此 邮件列表讨论。
从 Clojure 1.10 开始,协议可以选择通过每个值的元数据进行扩展
(defprotocol Component
:extend-via-metadata true
(start [component]))
当 :extend-via-metadata 为 true 时,值可以通过添加元数据来扩展协议,其中键是完全限定的协议函数符号,值是函数实现。协议实现首先检查直接定义(defrecord、deftype、reify),然后是元数据定义,然后是外部扩展(extend、extend-type、extend-protocol)。
(def component (with-meta {:name "db"} {`start (constantly "started")}))
(start component)
;;=> "started"