8

这是一个过于简单的示例:

我可以封装一个实现细节,例如使用一个原子作为计数器:

(defn make-counter
  ([] (make-counter 0))
  ([init-val]
   (let [c (atom init-val)]
     {:get (fn [] @c)
      :++ (fn [] (swap! c inc))})))

但这意味着我需要重新定义所有内容以添加功能(无继承):

(defn make-bi-counter
  ([] (make-bi-counter 0))
  ([init-val]
   (let [c (atom init-val)]
     {:get (fn [] @c)
      :++ (fn [] (swap! c inc))
      :-- (fn [] (swap! c dec))})))

而如果可以只扩展一个功能:

(assoc c :-- (env (:++ c) (fn [] (swap! c dec))))

(def c (make-counter))
(def b (make-bi-counter))
user=> ((:-- b))
-1
user=> ((:-- b))
-2
user=> ((:get b))
-2

或者我可以只暴露原子并拥有独立的功能:

(defn -- [a] (swap! a dec))
(def a (atom 0))
(-- a)

看来最好的选择是放弃封装,如果“继承”(或更准确地说:扩展)是可取的。

4

2 回答 2

16

是的,我认为惯用的 Clojure 是将数据与函数分开,正是因为您可以稍后编写新函数来处理旧数据。

将函数与数据绑定还意味着您以后无法在不更改或重新生成所有数据结构的情况下更改函数,因为您将在各处存储这些匿名函数。在 REPL 上进行交互式开发,我不想每次更改函数时都必须查找所有数据结构来修复它们。哈希映射中的闭包很聪明,但它们非常脆弱,除非有充分的理由,否则我不会走那条路。

定义你的接口(作为函数)只需要一点纪律,然后记住坚持你的接口而不是直接搞乱原子。目前还不清楚强行向自己隐瞒会得到什么好处。

如果你想要继承,多方法是一个很好的方法。

(defmulti getc type)
(defmulti ++ type)
(defmulti -- type)

(derive ::bi-counter ::counter)

(defn make-counter
  ([] (make-counter 0))
  ([init-val]
     (atom init-val :meta {:type ::counter})))

(defn make-bi-counter
  ([] (make-bi-counter 0))
  ([init-val]
     (atom init-val :meta {:type ::bi-counter})))

(defmethod getc ::counter [counter]
  @counter)

(defmethod ++ ::counter [counter]
  (swap! counter inc))

(defmethod -- ::bi-counter[counter]
  (swap! counter dec))

例如

user> (def c (make-counter))
#'user/c
user> (getc c)
0
user> (def b (make-bi-counter))
#'user/b
user> (++ c)
1
user> (++ b)
1
user> (-- b)
0
user> (-- c)
; Evaluation aborted.
;;  No method in multimethod '--' for dispatch value: :user/counter
于 2009-10-05T03:06:16.137 回答
0

我确定它不是惯用的 Clojure,但您绝对可以模拟受保护的变量。

在您的示例中, c是一个模拟的私有变量。如果您希望它成为受保护的变量,您需要以允许make-countermake-bi-counter访问它的方式定义它。将一个名为secret的哈希传递给make-counter。如果secret包含c,请使用它。否则创建自己的。

然后make-bi-counter可以创建一个包含c的秘密对象并将其传递给make-counter构造函数。现在make-bi-countermake-counter都可以访问同一个c

于 2010-11-21T16:38:53.137 回答