我有一个名为IExample
的协议,我定义了一个实现它的记录类型A
:
(defprotocol IExample
(foo [this] "do something")
(bar [this] "do something else"))
(defrecord A [field1 field2]
IExample
(foo [this]
(+ field1 field2))
(bar [this]
(- field1 field2)))
假设我想扩展另一个(基本)类型B
来实现此协议,但我知道如何从B
转换为A
:
(defn B-to-A
"converts a B object to an A object"
[Bobj] ...)
因为我有这个转换,我可以委托IExample
协议的所有调用
B
上的IExample
A
协议授权{/ p>}:
(extend B
IExample {
:foo (fn [this] (foo (B-to-A this)))
:bar (fn [this] (bar (B-to-A this)))})
然而,这似乎是一个非常多的样板(特别是对于更大的协议) 这不是clojure-idiomatic。
我怎样才能告诉clojure每次都隐含地将B
转换为A
使用IExample
函数在B
对象上调用B-to-A
函数?
答案 0 :(得分:2)
就样板文件而言,您可以编写一些宏来为您编写所有样板文件。另一方面,您可以在这里再次查看您的设计。
我们这里有3件事(类型):A
,B
和IExample
。然后我们在这些事物之间有两种关系:1)a-to-example : A -> IExample
2)b-to-a : B -> A
从中我们可以通过使用组合来获得第三关系,即compose b-to-a with a-to-example : B -> IExample
。现在,如果我们尝试将此设计转移到协议,我们会发现它不是一个简单的翻译,因为协议不会像上面设计中讨论的那样直接组成,而是我们可以使用如下所示的中间协议IToExample
:
(defprotocol IExample
(foo [this] "do something")
(bar [this] "do something else"))
(defprotocol IToExample
(to-example [this] "convert to IExample"))
(defrecord A [field1 field2]
IExample
(foo [this]
(+ field1 field2))
(bar [this]
(- field1 field2))
IToExample
(to-example [this] this))
(deftype B [])
(defn b-to-a [b] (A. ....))
(extend B
IToExample {:to-example b-to-a})
我们做了什么,我们在设计中将-> IExample
表示为具有一个函数的IToExample
协议。所以我们得到了:
a-to-example : A -> IExample
通过实施IToExample for A b-to-a : B -> A
compose b-to-a with a-to-example : B -> IExample
通过为B实施IToExample并使用b-to-a。 答案 1 :(得分:1)
这取决于。如果查看clojure核心seq函数,您可能会注意到ISec
接口只有4个方法,并且整个“公共”序列库由(更多)调用(some-internal-function (seq argument))
的函数定义 - 并且他们往往被明确记录为也这样做。从概念上讲,有一个像IExample接口这样的协议,以及一个描述seq
函数的附加协议,可以从某种类型转换为实现ISeq
的东西。
如果数据类型只需要实现几个方法(因此IExample可能很小),并且作用于协议的算法数量很大(因为您可以按常规编写所有这些方法),这是一个特别有用的策略函数)。