Несколько недель назад я поделился своей путаницей по поводу написания объектно-ориентированного 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, но когда вы это сделаете — будьте осторожны.