joseraya
6/8/2012 - 12:33 PM

Encapsulation in clojure

Encapsulation in clojure

(ns encapsulation.core-test
	(:use midje.sweet)
 	(:require [clj-time.core :as time]))

;My point here is to show how we can achieve encapsulation in clojure
;I assume that we may need encapsulation for two purposes:
; - Hide the internal representation of data (so that it can change)
; - Ensure the consistency of the data (by applying integrity constraints)

;Let's say that we have a "class" Person with an age.

(def person1 {:age 20})

(defn how-old? [p] (:age p))

(fact "person1 is 20 years old"
	(how-old? person1) => 20) 

;We know, though, that it is better to store the birth-date instead
;of the age, right?

(def person2 {:birth-date (time/date-time 1992 1 1)})

(fact "person2 is not 20 years old"
	(how-old? person2) =not=> 20) 

;We have broken our how-old? function because it depended on the internal representation of data
;Protocols allow us to specify the valid ways to interact with an "object"

(defprotocol HasAge 
	(age [x] "The number of years since it was born"))

;From now on, we will use age instead of how-old?. The advantage of age is that, for every "object"
;that wants to "instantiate" the protocol, it has the responsibility of providing a suitable 
;implementation of age

;We can reify the protocol with a very simple implementation
(def person3 (reify HasAge (age [_] 20)))

(fact "person3 is 20 years old"
	(age person3) => 20)

;Or we could reify it using a hash as our internal representation (I will "re-use" person2)
(def person4 
	(reify HasAge
		(age [_] (- (time/year (time/now)) (time/year (:birth-date person2))))))

(fact "person4 is 20 years old"
	(age person4) => 20)

;We can also have the interpreter generate a more efficient internal representation for us with
;records and types:

(defrecord Person [the-age]
	HasAge
	(age [_] the-age))

(def person5 (Person. 20))

(fact "person5 is 20 years old"
	(age person5) => 20)

 ;And, as a bonus, we have a place to add any validation logic about integrity constraints (the protocol) 
 ;and some nice functioality like keyword accessors or associative suup
(defproject encapsulation "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :repositories {"stuart" "http://stuartsierra.com/maven2"}
  :dependencies [[org.clojure/clojure "1.3.0"]
  [clj-time "0.4.2"]
  				[midje "1.4.0"]
  				 [com.stuartsierra/lazytest "1.2.3"]])