我正在试图弄清楚Clojure是否可以完全取代我在其他语言中习惯的范例。我不明白的一件事是如何在Clojure中以惯用方式实现封装(通过封装,我指的是使用对该数据进行操作的方法(或其他函数)捆绑数据)。
以下是OOP的用例:
var apple = {
type: "macintosh",
color: "red",
cost: 5
markup: 1.5
getInfo: function () {
return this.color + ' ' + this.type + ' apple';
}
getPrice: function(){
return this.cost * this.markup;
}
}
或类似地:
var person = {
birthdate: '8/30/1980',
firstname: 'leeroy',
middleinitial: 'b',
lastname: 'jenkins',
getAge: function () {
return -(new Date()
- new Date(this.birthdate));
}
getFullFormattedName: function () {
return capitalize(this.firstname+' '+this.middleinitial+' '+this.lastname;
}
}
以这种方式将行为与数据捆绑在一起通常很方便,但Clojure允许解决此问题的惯用方法是什么?
答案 0 :(得分:29)
在惯用语中,您的函数不属于数据,操作这些数据。使用结构maps或records代替结构,并定义将这些结构作为参数的函数。例如,您的苹果示例可能如下所示:
; 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中,您可以将 namespace 视为封装的一个单元,而不是数据结构。您可以创建命名空间,在其中定义数据结构,使用普通函数编写所需的所有功能,并将内部函数标记为私有(例如,使用defn-
形式定义它们,而不是defn
)。然后,所有非私有函数都将代表命名空间的接口。
如果您还需要多态性,可以查看multimethods和protocols。它们提供了ad-hoc和subtyping多种类型的方法,即覆盖函数行为 - 与Java继承和方法重载类似的事情。多方法更具动态性和强大性(您可以根据参数的任何函数的结果进行调度),但协议更高效,更直接(除了继承和可扩展性之外,它们与Java接口非常相似)。
更新:对其他答案的评论的回复:
我正在尝试确定取代方法的方法 OOP
了解“OOP的确切方法”是有帮助的。
传统的面向对象语言(如Java或特别是C ++)中的任何方法本质上都是普通函数,它采用名为this
的隐式参数。由于这个隐式参数,我们认为方法“属于”某个类,并且这些方法可以对它们“被调用”的对象进行操作。
然而,没有什么可以阻止你编写友好的全局函数(在C ++中)或公共静态方法(在Java中),它将对象作为其第一个参数并执行从方法可以做的所有事情。没有人这样做是因为多态,它通常是用方法的概念实现的,但我们现在没有考虑它。
由于Clojure没有任何“私有”状态的概念(除了Java互操作功能,但这是完全不同的东西),因此函数不需要以任何方式与它们操作的数据连接。您只需使用作为函数参数提供的数据,这就是全部。 Clojure中的多态功能以不同的方式完成(多方法和协议,参见上面的链接)而不是Java,尽管有一些相似之处。但这是另一个问题和答案的问题。
答案 1 :(得分:3)
在词法范围内创建 this 的闭包的哈希映射。它与您原始的Javascript代码没有什么不同。
(defn apple-object [this]
{:get-info #(str (this :color) " " (this :type) " apple")
:get-price #(* (this :cost) (this :markup))})
(defn person-object [this]
{:get-age
#(- (-> (java.util.Date.) (.getTime))
(-> (this :birthdate) (.getTime)))
:get-full-formatted-name
#(clojure.string/join
" "
(map clojure.string/capitalize
[(this :firstname) (this :middleinitial) (this :lastname)]))})
;;;; usage ;;;;
(def apple (apple-object
{:type "macintosh"
:color "red"
:cost 5
:markup 1.5}))
(apple :type)
((apple :get-info))
((apple :get-price))
(def person (person-object
{:birthdate (java.util.Date. 80 7 30)
:firstname "leeroy"
:middleinitial "b"
:lastname "jenkins"}))
(person :birth-date)
((person :get-age))
((person :get-full-formatted-name))
答案 2 :(得分:-4)
不要这样做。你正试图在FP中编写OOP。与用英语写中文一样糟糕。