Несколько недель назад я поделился своей путаницей по поводу написания объектно-ориентированного ClojureScript и небольшой библиотеки под названием cljs-painkiller . Благодаря удивительному сообществу Clojure / ClojureScript я скоро узнал гораздо лучшие способы сделать это.
Пример обезболивающего
Я пожаловался, что мне пришлось написать ClojureScript, который выглядит так:
(defn Bag [] (this-as this (set! (.-store this) (array)) this)) (set! (.. Bag -prototype -add) (fn [val] (this-as this (.push (.-store this) val)))) (set! (.. Bag -prototype -print) (fn [] (this-as this (.log js/console (.-store this))))) (def mybag (Bag.)) (.add mybag 5) (.add mybag 7) (.print mybag)
Неправильно! Вскоре после того, как эта статья появилась на DZone, Дэвид Нолен ( @swannodette ) показал мне несколько фрагментов в простом ClojureScript, которые делают то же самое:
(deftype Bag [store] Object (add [_ x] (.push store x)) (print [_] (.log js/console store))) (defn bag [arr] (Bag. arr)) (defn bag [store] (reify Object (add [this x] (.push store x)) (print [this x] (.log js/console store))))
Намного лучше, не так ли? И он компилируется в довольно идиоматический, совместимый JavaScript, а не в какую-то магию более высокого уровня.
Я в раздумье о необходимости разоблачать store
вот так. С одной стороны, это делает все изменчивые вещи явными. С другой стороны, это означает, что вы выставляете много «личных» вещей потребителю, даже требуя этого от него. Чтобы справиться с этим, вы можете скрыть создание массива в функции конструктора:
(defn bag [] (Bag. (array))) (defn bag [] (let [store (array)] (reify Object (add [this x] (.push store x)) (print [this x] (.log js/console store)))))
Пересмотренный пример магистрали
Когда я только начинал с ClojureScript, я поделился примером интеграции с Backbone . Затем я пожаловался, что он совершенно непригоден для использования с любым менее тривиальным кодом Backbone.
Вот как выглядел мой образец:
(def MyModel (.extend Backbone.Model (js-obj "promptColor" (fn [] (let [ css-color (js/prompt "Please enter a CSS color:")] (this-as this (.set this (js-obj "color" css-color)))))))) (def my-model (MyModel.))
Оказывается, его можно переписать так:
(def MyModel (.extend Backbone.Model (reify Object (promptColor [this] (let [ css-color (js/prompt "Please enter a CSS color:")] (.set this (js-obj "color" css-color))))))) (def my-model (MyModel.))
Много шума прошло. Похоже, что такой reify
вызов — путь в этом случае.
Спасен?
Мне нравится, когда сообщество доказывает, что это неправильно, и, очевидно, есть лучшие способы сделать это, чем я думал изначально На самом деле, когда я начал свое приключение с ClojureScript, я поссорился с компилятором — теперь я наконец начинаю понимать, что я делаю.
ClojureScript требует некоторой церемонии вокруг создания объекта, отделения поведения от состояния и его инициализации. В некоторых контекстах это слишком ограничительно, в некоторых — просто отлично.
Мой последний пример — шип Knockout, где у меня был такой JavaScript:
function AppViewModel() { this.firstName = ko.observable("Bert"); this.lastName = ko.observable("Bertington"); this.fullName = ko.computed(function() { return this.firstName() + " " + this.lastName(); }, this); this.capitalizeLastName = function() { var currentVal = this.lastName(); this.lastName(currentVal.toUpperCase()); }; } ko.applyBindings(new AppViewModel());
Для тех, кто не знаком с нокаутом и firstName
т. Д., Есть методы. Особенно интересными являются методы fullName
и capitalizeLastName
. Здесь у нас есть метод, созданный вызовом to ko.computed
, упаковывающий функцию, которая ссылается на другие методы this
объекта. Не так уж плохо на языке OO …
… Но в ClojureScript, видимо, лучшее, что вы можете сделать, — это то, что я сделал в начале работы:
(def my-model (js-obj "firstName" (.observable js/ko "Bert") "lastName" (.observable js/ko "Bertington") "fullName" (this-as this (.computed js/ko (fn [] this (.firstName this)), this)))) (.applyBindings js/ko my-model)
Мне это совсем не нравится. Здесь я думаю, что макросы действительно необходимы. Это может быть болеутоляющее средство или какие-то специальные макросы только для интеграции с нокаутом .
ClojureScript не всегда нуждается в болеутоляющем, но по мере того, как ваш код становится более интересным, макросов ваш выход может быть неизбежным. Я думаю, вам не всегда нужно писать такой код OO, но когда вы это сделаете — будьте осторожны.