2009-10-05 13 views
8

Aquí es un ejemplo simplista de la ilustración:Clojure permite la encapsulación y la herencia, pero ¿puedo combinarlas?

puedo encapsular un detalle de implementación, tales como el uso de un átomo de un contador:

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

Pero eso significa que necesito para redefinir todo para añadir una característica (sin herencia):

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

Mientras que si fuera posible simplemente extender la función:

(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 

O Me podría haber expuesto el átomo y tenía funciones independientes:

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

Parece ser la mejor opción es renunciar a la encapsulación, si 'herencia' (o quizás más exactamente: la extensión) es deseable.

Respuesta

16

Sí, creo que idiomático Clojure es separar sus datos de sus funciones, precisamente por la razón por la que luego puede escribir nuevas funciones para trabajar con los datos antiguos.

El agrupamiento de funciones con datos también significa que no puede modificar sus funciones más tarde sin alterar o volver a generar todas sus estructuras de datos, ya que tendrá estas funciones anónimas almacenadas por todos lados. Desarrollando de forma interactiva en un REPL, odiaría tener que buscar todas mis estructuras de datos para arreglarlas cada vez que cambio una función. Los cierres en los hash-maps son inteligentes, pero son bastante frágiles y no seguiría esa ruta a menos que hubiera una buena razón.

Solo se necesita un poco de disciplina para definir su interfaz (como funciones) y luego recordar mantener su interfaz y no perder el átomo directamente. No está claro qué beneficio obtendrás si te ocultas a la fuerza cosas tuyas.

Si desea herencia, los métodos múltiples son una buena manera de hacerlo.

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

e.g.

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

Creo que la encapsulación no se trata de "esconder cosas de uno mismo". Se trata de comunicar su diseño a otros antes que nada. – Alexey

0

Estoy seguro de que no es idiomático Clojure pero definitivamente puede simular variables protegidas.

En su ejemplo c es una variable privada simulada. Si desea que sea una variable protegida, debe definirla de forma que permita make-counter y make-bi-counter para acceder a ella. Pase un hash llamado secret en make-counter. Si secret contiene c, úselo. Crea lo tuyo de lo contrario.

Entonces make-bi-counter puede crear un objeto secreto que contiene c y pasarlo a la maquillaje contador constructor.Ahora tanto make-bi-counter y make-counter tienen acceso a la misma c.

Cuestiones relacionadas