如果您在类型或类上进行调度,请考虑修改您的代码以使用协议。
更详细的答案:
如果还注册了精确的子类型, Clojuredefmulti
不允许您分派到父 Java 类型。这是故意的(在Liskov 替代原则辩论中,它站在“最小惊喜”的一边)。由于已经为 Y 注册了多方法,如果您的对象isa?
恰好是 Y,那么您将点击 Y 的方法——即使它也是一个“X”。在 Java 中,类可以从多个接口继承,它们只能是一种类型。这就是你在这里遇到的问题。
根据多方法的文档
派生是由 Java 继承(对于类值)或使用 Clojure 的 ad hoc 层次系统的组合确定的。层次系统支持名称(符号或关键字)之间的派生关系,以及类和名称之间的关系。派生函数创建这些关系,并且isa?
函数测试它们的存在。注意isa?
不是instance?
。
如果您查看 的源代码MultiFn
,您会发现 Clojure 总是使用给定的多方法调度值最具体的 Java 类(在类型上调度时)。
请参阅Clojure 1.4.0 源代码中的这一行,MultiFn
特别是dominates
.
在 REPL:
user=> (defmulti foo #(class %))
user=> (defmethod foo java.util.RandomAccess [x] "RandomAccess")
user=> (defmethod foo java.util.Vector [x] "Vector")
user=> (defmethod foo java.util.Stack [x] "Stack")
好的,这按预期工作,并且prefer-method
不能覆盖类层次结构,因为isa?
首先检查 Java 类型。
user=> (prefer-method foo java.util.RandomAccess java.util.Stack)
user=> (foo (new java.util.Stack))
"Stack"
最后,在源代码中检查MultiFn
与调度值类型匹配的所有方法键(按isa?
)。如果找到多个匹配项,它会检查类型层次结构中的“支配”值。我们在这里看到Stack
占主导地位的RandomAccess
user=> (isa? java.util.Stack java.util.RandomAccess)
true
user=> (isa? java.util.RandomAccess java.util.Stack)
false
现在,如果我定义一个新方法bar
如下:
user=> (defmulti bar #(class %))
user=> (defmethod bar Comparable [x] "Comparable")
user=> (defmethod bar java.io.Serializable [x] "Serializable")
由于模棱两可,我得到以下信息:
user=> (bar 1)
IllegalArgumentException Multiple methods in multimethod 'bar' match dispatch value: class java.lang.Long -> interface java.lang.Comparable and interface java.io.Serializable, and neither is preferred clojure.lang.MultiFn.findAndCacheBestMethod (MultiFn.java:136)
现在,我可以解决这个问题prefer-method
user=> (prefer-method bar Comparable java.io.Serializable)
user=> (bar 1)
"Comparable"
但是,如果我为Long
user=> (defmethod bar Long [x] "Long")
user=> (bar 1)
"Long"
Comparable
即使我使用,我也无法返回prefer-method
:
user=> (prefer-method bar Comparable Long)
user=> (bar 1)
"Long"
这似乎是你在这里遇到的。
请注意,您可以选择remove-method
- 但我认为与您设计的“黑客”相比,这是一个更重量级/更危险(猴子补丁?)的解决方案。