Идиоматическая собственность изменила уведомление в Scala?
Вопрос
Я пытаюсь найти более чистой альтернативой (то есть идиоматично для Scala) к тому, что вы видите с привязкой данных в WPF / Silverlight-Binding - то есть реализация INOTIFYPROPERTYCHANTED. Во-первых, немного фона:
В приложениях .NET WPF или Silverlight у вас есть концепция двусторонней передачи данных (то есть, привязав значение некоторого элемента UI к свойство DataContext в таком способе, который изменяется в элемент UI повлиять на свойство и наоборот. Один из способов включить это, чтобы это реализовать интерфейс inotifyPropertyChanged в вашем dataContext. К сожалению, это вводит много кода котел для любого свойства, которое вы добавляете в тип «ModelView». Вот как это может посмотреть в Scala:
trait IDrawable extends INotifyPropertyChanged
{
protected var drawOrder : Int = 0
def DrawOrder : Int = drawOrder
def DrawOrder_=(value : Int) {
if(drawOrder != value) {
drawOrder = value
OnPropertyChanged("DrawOrder")
}
}
protected var visible : Boolean = true
def Visible : Boolean = visible
def Visible_=(value: Boolean) = {
if(visible != value) {
visible = value
OnPropertyChanged("Visible")
}
}
def Mutate() : Unit = {
if(Visible) {
DrawOrder += 1 // Should trigger the PropertyChanged "Event" of INotifyPropertyChanged trait
}
}
}
Ради пространства, давайте предположим, что тип INOTIFYPROPERTYCHANTED - это черта, которая управляет списком обратных вызовов типа (AnyRef, String) => единицы, и что onPropertyChanged - это метод, вызывающий все эти обратные вызовы, проходящие «это» как AnyRef и пропущенная строка). Это просто было бы событием в C #.
Вы можете сразу увидеть проблему: это тонна кода котельной только для двух свойств. Я всегда хотел написать что-то вроде этого вместо этого:
trait IDrawable
{
val Visible = new ObservableProperty[Boolean]('Visible, true)
val DrawOrder = new ObservableProperty[Int]('DrawOrder, 0)
def Mutate() : Unit = {
if(Visible) {
DrawOrder += 1 // Should trigger the PropertyChanged "Event" of ObservableProperty class
}
}
}
Я знаю, что я могу легко написать это так, если наблюдаетсяПробрестно [T] имеет значение / значение_ = методы (это метод, который я использую сейчас):
trait IDrawable {
// on a side note, is there some way to get a Symbol representing the Visible field
// on the following line, instead of hard-coding it in the ObservableProperty
// constructor?
val Visible = new ObservableProperty[Boolean]('Visible, true)
val DrawOrder = new ObservableProperty[Int]('DrawOrder, 0)
def Mutate() : Unit = {
if(Visible.Value) {
DrawOrder.Value += 1
}
}
}
// given this implementation of ObservableProperty[T] in my library
// note: IEvent, Event, and EventArgs are classes in my library for
// handling lists of callbacks - they work similarly to events in C#
class PropertyChangedEventArgs(val PropertyName: Symbol) extends EventArgs("")
class ObservableProperty[T](val PropertyName: Symbol, private var value: T) {
protected val propertyChanged = new Event[PropertyChangedEventArgs]
def PropertyChanged: IEvent[PropertyChangedEventArgs] = propertyChanged
def Value = value;
def Value_=(value: T) {
if(this.value != value) {
this.value = value
propertyChanged(this, new PropertyChangedEventArgs(PropertyName))
}
}
}
Но есть ли способ реализовать первую версию, используя непливные или некоторые другие функции / идиомы Scala, чтобы сделать наблюдаемые экземпляры функции, как если бы они были регулярными «свойствами» в Scala, не требуя отзыва к способам значений? Единственная вещь, которую я могу подумать, это что-то подобное, что является более многословным, чем любая из двух вышеуказанных версий, но все еще меньше многословного, чем оригинал:
trait IDrawable {
private val visible = new ObservableProperty[Boolean]('Visible, false)
def Visible = visible.Value
def Visible_=(value: Boolean): Unit = { visible.Value = value }
private val drawOrder = new ObservableProperty[Int]('DrawOrder, 0)
def DrawOrder = drawOrder.Value
def DrawOrder_=(value: Int): Unit = { drawOrder.Value = value }
def Mutate() : Unit = {
if(Visible) {
DrawOrder += 1
}
}
}
Решение
Я не мог утверждать, что это каноническая структура изменения свойств в Scala, но я использовал такой класс до:
abstract class Notifier[T,U](t0: T) {
import java.util.concurrent.atomic.AtomicReference
import scala.actors.OutputChannel
type OCUT = OutputChannel[(U,AtomicReference[T])]
val data = new AtomicReference[T](t0)
def id: U
protected var callbacks = Nil:List[T => Unit]
protected var listeners = Nil:List[OCUT]
def apply() = data.get
def update(t: T) {
val told = data.getAndSet(t)
if (t != told) {
callbacks.foreach(_(t))
listeners.foreach(_ ! (id,data))
}
}
def attend(f: T=>Unit) { callbacks ::= f }
def attend(oc: OCUT) { listeners ::= oc }
def ignore(f: T=>Unit) { callbacks = callbacks.filter(_ != f) }
def ignore(oc: OCUT) { listeners = listeners.filter(_ != oc) }
}
Мотивация для создания этого класса состояла в том, что я хотел, чтобы я хотел использовать гибкий поток, безопасный способ реагировать на изменения, которые это обеспечивает (поскольку оно обеспечивает как обратные вызовы, так и могут подталкивать сообщения для актеров).
Мне кажется - если я не понимаю, что именно вы хотите, потому что у меня нет смысла изучать материал WPF / Silverlight - что это может реализовать все, что вы хотите и больше.
Например,
class IDrawable extends SomethingWithOnPropertyChanged {
val drawOrder = new Notifier[Int,Symbol](0) { def id = 'DrawOrder }
val visible = new Notifier[Boolean,Symbol](false) { def id = 'Visible }
drawOrder.attend((i:Int) => OnPropertyChanged(drawOrder.id))
def mutate {
if (visible()) drawOrder() += 1
}
}
должно быть грубо эквивалентно тому, что вы хотите. (Опять же, я не уверен, насколько гибко вы хотите, чтобы это было; вы могли бы создать набор символов -> сопоставления новирования, которые вы сможете посмотреть с методом приложения, поэтому цель будет иметь более простое время для чего-то, когда он получает Символ носителя.)
Единственным существенным отличием вашего использования является то, что уведомитель использует свои методы Apply / Update для экономии котельной; Вам не нужно писать def x и def x_ = методы каждый раз, но вы должны использовать () для доступа.