在惯用的 clojure 中,您的函数不属于数据,它们对这些数据进行操作。使用映射或记录代替结构,并定义将这些结构作为参数的函数。例如,您的苹果示例可能如下所示:
; Define a record type with given fields
; `defrecord` macro defines a type and also two constructor functions,
; `->apple` and `map->apple`. The first one take a number of arguments
; corresponding to the fields, and the second one takes a map with
; field names as keys. See below for examples.
(defrecord apple [type color cost markup])
; Define several functions working on apples
; Note that these functions do not have any kind of reference to the datatype,
; they exploit map interface of the record object, accessing it like a map,
; so you can supply a real map instead of record instance, and it will work
(defn get-info [a] (str (:color a) " " (:type a) " apple"))
(defn get-price [a] (* (:cost a) (:markup a)))
; Example computation
; Bind `a` to the record created with constructor function,
; then call the functions defined above on this record and print the results
(let [a (->apple "macintosh" "red" 5 1.5)
a-info (get-info a)
a-price (get-price a)]
(println a-info a-price))
; Will print the following:
; red macintosh apple 7.5
; You can also create an instance from the map
; This code is equivalent to the one above
(let [a (map->apple {:type "macintosh" :color "red" :cost 5 :markup 1.5})
a-info (get-info a)
a-price (get-price a)]
(println a-info a-price))
; You can also provide plain map instead of record
(let [a {:type "macintosh" :color "red" :cost 5 :markup 1.5}
a-info (get-info a)
a-price (get-price a)]
(println a-info a-price))
通常,当您想要具有可从 Java 代码获得的已知字段的静态对象时使用记录(defrecord
生成适当的类;它还具有许多其他功能,如上面的链接所述),并且在所有其他情况下使用映射 - 关键字参数,中间结构、动态对象(例如从 sql 查询返回的对象)等。
因此在 clojure 中,您可以将命名空间视为一个封装单元,而不是数据结构。您可以创建一个命名空间,在其中定义您的数据结构,使用普通函数编写您想要的所有功能并将内部函数标记为私有(例如,使用defn-
form 定义它们,而不是defn
)。然后所有非私有函数将代表您的命名空间的接口。
如果您还需要多态性,则可以查看multimethods and protocols。它们为特殊和子类型类型的多态性提供了手段,即覆盖函数行为——类似于 Java 继承和方法重载可以做的事情。多方法更加动态和强大(您可以对参数的任何函数的结果进行分派),但协议更加高效和直接(除了继承和可扩展性之外,它们与 Java 接口非常相似)。
更新:回复您对其他答案的评论:
我正在尝试确定替代 OOP 方法的方法
了解究竟什么是“OOP 方法”是有帮助的。
传统面向对象语言(如 Java 或特别是 C++)中的任何方法本质上都是一个普通函数,它接受一个名为. 由于这个隐含的论点,我们认为方法“属于”某个类,并且这些方法可以对它们“调用”的对象进行操作。this
但是,没有什么能阻止您编写友好的全局函数(在 C++ 中)或公共静态方法(在 Java 中),它们将对象作为其第一个参数并执行方法中可能执行的所有操作。没有人这样做是因为多态性,这通常是通过方法的概念来实现的,但我们现在不考虑它。
由于 Clojure 没有任何“私有”状态的概念(Java 互操作功能除外,但这是完全不同的事情),函数不需要以任何方式与它们操作的数据连接。您只需使用作为函数参数提供的数据,仅此而已。Clojure 中的多态功能以与 Java 不同的方式(多方法和协议,请参见上面的链接)完成,尽管有一些相似之处。但这是另一个问题和答案的问题。