суббота, 21 мая 2011 г.

Модификация атрибутов объекта в Clojure

Часто необходимо модифицировать состояние объекта, особенно когда работаешь с java библиотекой, смысл которой сводится к вызову callback'ов и необходимо сохранять промежуточное состояние между вызовами. Например SAX парсер.

Clojure определяет три способа работы с изменяемыми данными.  Это ссылки, агенты и атомы. Все они описаны в статье Алексея Отта (http://alexott.net/ru/clojure/clojure-intro/).
Для моего случая подходит синхронное изменение и заворачивать обращения  к изменяемым переменным в транзакции нет необходимости. Поэтому я буду использовать атомы(atoms).

Для примера опишем класс com.Example. Объявим одновременно пространство имен и класс(com.Example) с помощью макроса ns.

(ns com.Example
  (:gen-class
    :state state
    :init init
    :constructors {[] []}
    :methods [[setAttr [clojure.lang.Keyword int] void],
              [getAttr [clojure.lang.Keyword] int],
              [removeAttr [clojure.lang.Keyword] void]]
    :main false))

:init - имя конструктора
:state - имя атрибута для хранения состояния объекта
:constructors - описание типов параметров конструкторов класса
:methods - описание доступных методов класса, в формате [имя [тип_параметра ...] тип_возвращаемого_значения]
:main - false - не генерировать, автоматически, метод main(первый метод который будет вызван для java программы)

Теперь код объявленных выше методов.

Конструктор:

(defn -init []
[[] (atom {})])

конструктор без параметров. Конструктор класса в clojure должен вернуть вектор с двумя элементами.

[ [superclass-constructor-args] state]

Первый элемент -  еще один вектор, содержит параметры конструктора родительского класса([] - для родительского конструктора без параметров). Второй элемент - значение переменной состояния(имя которой определяется через параметр :state). В нашем случае это хеш(maps). Применение функции atoms делает состояние объекта изменяемым.

После того как state в классе объявлен как изменяемый его можно модифицировать функцией swap!.

Атрибуты объекта будут хранится как ключевые слова(keywords) а значения - это целые числа.
Методы для сохранения и получения значения атрибутов объекта будут такими:


(defn -setAttr [this attr value]
  (swap! (.state this) assoc attr value))

(defn -getAttr [this attr]
  (get @(.state this) attr))

(defn -removeAttr [this attr]
  (swap! (.state this) dissoc attr))


И маленький пример использования
user=> (def ex (com.Example.))
#'user/ex
user=> (.state ex)
#<Atom@724eb043: {}>
user=> ex
#<Example com.Example@30c06258>
user=> (.setAttr ex :a 1)
nil
user=> (.setAttr ex :b 2)
nil
user=> (.setAttr ex :super-key -9)  
nil
user=> (.state ex)
#<Atom@724eb043: {:super-key -9, :b 2, :a 1}>
user=> (.getAttr ex :b)
2
user=> (.removeAttr ex :super-key)
nil
user=> (.state ex)                
#<Atom@724eb043: {:b 2, :a 1}>

Комментариев нет:

Отправить комментарий