2009-10-05 5 views
8

Hier ist ein allzu einfaches Beispiel zur Veranschaulichung:Clojure ermöglicht die Kapselung und Vererbung, aber kann ich sie kombinieren?

ich eine Implementierung Detail, wie unter Verwendung eines Atom für einen Zähler einkapseln kann:

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

Aber das bedeutet, dass ich alles neu zu definieren brauchen eine Funktion hinzuzufügen (keine Vererbung):

(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))}))) 

Während, wenn es möglich wäre, zu verlängern nur die eine Funktion:

(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 

Oder ich nur das Atom ausgesetzt haben könnte und unabhängige Funktionen hatte:

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

Es scheint die beste Option ist Verkapselung zu verzichten, wenn ‚Vererbung‘ (oder vielleicht genauer: Erweiterung) wünschenswert ist.

Antwort

16

Ja, ich denke, idiomatische Clojure ist es, Ihre Daten von Ihren Funktionen zu trennen, gerade deshalb, weil Sie später neue Funktionen schreiben können, um an den alten Daten zu arbeiten. Die Bündelung von Funktionen mit Daten bedeutet auch, dass Sie Ihre Funktionen später nicht ändern können, ohne alle Ihre Datenstrukturen zu ändern oder neu zu generieren, da Sie diese anonymen Funktionen überall gespeichert haben. Wenn ich mich interaktiv an einer REPL entwickle, würde ich es hassen, alle meine Datenstrukturen aufspüren zu müssen, um sie jedes Mal zu reparieren, wenn ich eine Funktion ändere. Verschlüsse in Hash-Maps sind schlau, aber sie sind ziemlich zerbrechlich und ich würde diesen Weg nicht gehen, außer es gibt einen wirklich guten Grund.

Es erfordert nur ein Minimum an Disziplin, um Ihre Schnittstelle zu definieren (als Funktionen) und dann denken Sie daran, sich an Ihre Schnittstelle zu halten und nicht direkt mit dem Atom herumzuspielen. Es ist unklar, welchen Nutzen Sie davon haben werden, Dinge gewaltsam vor sich selbst zu verbergen.

Wenn Sie Vererbung wollen, sind Multimethoden ein guter Weg, um es zu tun.

(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)) 

z.B.

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 
+0

Ich glaube, Verkapselung bedeutet nicht, Dinge vor sich selbst zu verbergen. Es geht darum, dein Design zuallererst mit anderen zu teilen. – Alexey

0

Ich bin sicher, es ist nicht idiomatische Clojure, aber Sie können auf jeden Fall geschützte Variablen simulieren.

In Ihrem Beispiel c ist eine simulierte private Variable. Wenn Sie möchten, dass es sich um eine geschützte Variable handelt, müssen Sie sie so definieren, dass sowohl make-counter als auch make-bi-counter darauf zugreifen können. Übergeben Sie einen Hash mit der Bezeichnung geheim in make-counter. Wenn geheim enthält c, verwenden Sie das. Erstelle sonst dein eigenes.

Dann Make-bi-counter kann ein secret Objekt erstellen, die c enthält und es in den Konstruktormake-Zähler bestehen.Jetzt haben beide make-bi-counter und make-counter haben Zugriff auf die gleichen c.