总的来说,您不经常使用协议、记录和自定义类型。对于那些大多应用于特定应用域的逻辑,使用传统的映射和常规函数即可。尽管如此,我会尽量解释何时需要使用它们。
总结:
当您需要在内部实现可变的数据容器时,或需要某种形式的数据封装时,请使用 deftype。这主要用于非常基础的结构,如数据结构和引用类型。
如果您有一个在运行时调用不同事情的功能,并且它执行的操作必须针对它被调用的内容是特定的。那么,您想使用协议或多态方法。如果您可以根据第一个参数的类型来决定您要执行的操作,请使用协议;如果不可以,请使用多态方法。
如果您有一个根据 Map 类型执行不同事情的功能。例如,您可能需要一个标记 Map 代表某物,如 Person、Car、User 等。并且您有需要用不同类型的 Map 来调用的功能,并希望它们基于类型选择要执行的操作,则请使用 defrecord。
如果您有一个需要根据其调用的参数数量执行不同事情的功能,请使用多参数或变参。
最后,如果您发现自己正在编写许多以相同名称开始但以某种与您调用它的类型相关的区分符结束的功能,例如:`add-user`、`add-role`、`add-item`,这可能表明您可以使用协议或多态方法和单个 `add` 函数来对此进行建模。
deftype
我从 deftype 开始。您几乎从不使用它。deftype 允许您实现新的抽象数据类型(ADT)。它用于创建新类型的数据库。基本上,任何需要封装数据并通过一个安全界面包裹并确保底层数据完整性的安全接口来提供数据访问的功能。
例如,如果您想要添加一个新的数据结构,比如您需要实现一个双链表,您可以使用 deftype。
现在,您几乎不需要它,因为大部分有用的数据容器已经在核心、互操作性或库中为您实现。例如,Java 已经提供了双链表:[https://docs.oracle.com/javase/8/docs/api/java/util/LinkedList.html](https://docs.oracle.com/javase/8/docs/api/java/util/LinkedList.html)
为了更好地理解,deftype 允许数据封装。在一般情况下,Clojure 中不需要数据封装,因为默认的数据结构和绑定都是不可变的,因此将数据暴露给外部作为只读没有风险。但是,对于实现数据容器本身,如不可变数据结构或各种引用类型(如 Atom),您需要突变来提供空间和时间效率高的实现。在这种情况下,您不应该让外部用户可以随意操作数据,因为他们可以轻易打破所需的不变性。因此,您希望提供一个抽象接口,例如对于双向链表,您可能有 add-first (添加首部)、add-last (添加尾部)、remove (移除)、next (下一个)、prev (上一个) 等。因此,deftype 对添加任何形式的数据容器到语言中非常有用,您几乎不需要手动这样做。
defrecord
当您需要创建一个自定义类型并为一些 Map 添加语义意义时,就会用到 defrecord。记录本质上只是一个 Map,但将它的类型替换为记录类型名称。因此,将类型从 Map 替换为记录名称。
如果您想有一个 Person 类型的 Map,您将使用 defrecord 来实现。
这只有在需要一段代码在运行时根据类型进行不同的操作时才有用。如果有一段代码将接收到不同类型的 Maps,您希望它对每种类型都执行不同的操作,并且希望有更好的性能。
我在这里这样说,因为实际上有几种提供这种动态功能的方法。一种是用原生类型,由记录和协议提供。另一种是手动建模类型和使用 defmulti。后者更强大,因为您可以以任何想要的方式表示类型,类型甚至可以从数据结构本身或数据值中推断出来。另一方面,它将更快。
defrecord 只允许函数根据其第一个参数的类型执行不同的事情,并且记录的结构和值不能决定其类型。这通常被称为命名类型。您的记录是某种类型,因为您明确地这样命名了。另一方面,defmulti 允许使用鸭子类型,即一个 Map 可以是某种类型,因为它有内在的结构和/或包含的值。
defprotocol
当您想要根据函数第一个参数的本地类型执行不同的操作时,会用到 defprotocol。比如,一个将被不同的记录调用,并为每个不同的记录类型做不同的事情的函数。
defmulti
正如我在简要介绍记录时解释的那样。如果您想要一个函数根据第一个参数的类型之外的东西执行不同的操作,例如其他参数的类型或数据的结构,或者任何参数的值,您会使用 defmulti 而不是 defprotocol。
多参数
此外,如果您只希望根据调用的函数的参数数量执行不同的操作,只需使用多参数函数即可。