Clojure: возможно ли наследовать состояние класса, определенного с помощью: gen-class?

Я пытаюсь изучить некоторую библиотеку Java с Clojure в качестве рабочего языка. Библиотека (как обычно в Java) очень объектно-ориентирована и нуждается в иерархии классов в клиентском коде. Я определил класс, унаследованный от класса библиотеки, с некоторыми дополнительными методами и данными, хранящимися в виде изменяемого словаря в поле state:

(:gen-class
   :name my-project.my-parent-class.MyParentClass
   :extends com.example.library.LibraryClass
   :methods [[setSomeData [com.example.library.LibraryType] void]]
   :exposes-methods {libraryMethodOne parentLibraryMethodOne
                     libraryMethodTwo parentLibraryMethodTwo}
   :init init
   :state state))

(defmacro set-field!
  [this key value]
  `(dosync (alter (.state ~this) assoc ~key ~value)))

(defmacro get-field
  [this key]
  `(@(.state ~this) ~key))

(defn -init []
  [[]
   (ref {:library-object-one (LibraryObjectOne.)
         :library-object-two (LibraryObjectTwo.)})])

(defn -setSomeData [this t]
  (.setSomething (get-field this :library-object-one) t)

… ; (library methods overriding here)

Затем я создал дочерний класс, унаследованный от моего MyParentClass:

(:gen-class
   :name my-project.my-child-class.ChildClass
   :extends my-project.my-parent-class.MyParentClass
   :exposes-methods {libraryMethodOne myParentClassMethodOne}
   :init init
   :state state))

(defn -init []
  [[] (ref {})])
…

Но я получаю исключение нулевого указателя, когда я вызываю макрос (get-field this :library-object-one) для экземпляра ChildClass в методе -setSomeData — поле, определенное :state, не наследуется, и в словаре нет ключа :library-object-one.

Быстрое и грязное исправление заключается в переопределении функции -init в дочернем классе следующим образом:

(defn -init []
  [[] (ref {:library-object-one (LibraryObjectOne.)
            :library-object-two (LibraryObjectTwo.)})])

(т.е. скопировать код инициализации из родительского класса). Но это ужасное нарушение принципа DRY. Есть ли способ наследовать состояние от родительского класса?

Я понимаю, что это вовсе не идиоматический Clojure, а своего рода злоупотребление :gen-class API, которое предоставляется только для целей взаимодействия. Возможно, мне не следует использовать наследование на своей стороне, и я должен реализовать полиморфизм каким-либо не-ООП-способом (например, путем изменения функций и значений, хранящихся в словаре state). Если это правда, где я могу увидеть хорошие примеры такого подхода?


person Yuriy Al. Shirokov    schedule 06.11.2018    source источник
comment
Что-то вроде вызова конструктора родительского класса можно сделать, вернув [[] (nth (my-project.my-parent-class/-init) 1)] из функции -init дочернего класса. Но это все еще выглядит как кладж для меня.   -  person Yuriy Al. Shirokov    schedule 06.11.2018


Ответы (1)


Вам не нужно указывать :state для подклассов. Если вы этого не сделаете, он просто вызовет родительский метод.

(ns my-project.classes)

(gen-class
  :name my_project.my_parent_class.MyParentClass
  :init init
  :state state)

(defn -init []
      [[]
       (ref {:library-object-one "foo"
             :library-object-two "bar"})])

(gen-class
  :name my_project.my_child_class.ChildClass
  :extends my_project.my_parent_class.MyParentClass)

И вызывающее пространство имен:

(ns my-project.core
  (:import (my_project.my_child_class ChildClass))
  (:gen-class))

(defn -main [& args]
  (let [inst (ChildClass.)]
    (println @(.state inst))))

Это печатает:

{:library-object-one foo, :library-object-two bar}

person Brian Baritonehands Gregg    schedule 06.11.2018