这是一个过于简单的例子:
我可以封装一个实现细节,例如使用原子作为计数器:
(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)
如果需要'继承'(或者更准确地说:扩展),似乎最好的选择是放弃封装.
是的,我认为惯用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