идиоматическое свойство изменило уведомление в scala? - PullRequest
11 голосов
/ 26 мая 2010

Я пытаюсь найти более чистую альтернативу (идиоматическую для Scala) тому, что вы видите с привязкой данных в привязке данных WPF / silverlight - то есть реализацию INotifyPropertyChanged.Во-первых, немного предыстории:

В приложениях .Net WPF или silverlight у вас есть концепция двусторонней привязки данных (то есть привязка значения некоторого элемента пользовательского интерфейса к свойству .net объектаDataContext таким образом, что изменения в элементе пользовательского интерфейса влияют на свойство, и наоборот. Один из способов включить это - реализовать интерфейс 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
          }
      }
}

Ради пространства, давайте предположим, что тип INotifyPropertyChanged - это черта, которая управляет списком обратных вызовов типа (AnyRef, String)=> Unit, и этот OnPropertyChanged является методом, который вызывает все эти обратные вызовы, передавая «this» как AnyRef и переданную String).Это было бы просто событием в 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
          }
      }
}

Я знаю, что могу легко написать это так, если ObservableProperty [T] имеет методы Value / Value_ = (это метод I 'м, используя сейчас):

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, чтобы заставить экземпляры ObservableProperty функционировать так, как если бы они были обычными «свойствами» в scala,без необходимости вызывать методы Value?Единственное, о чем я могу думать, это что-то вроде этого, более подробное, чем любая из двух вышеуказанных версий, но все же менее подробное, чем оригинал:

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 
    }
  }
}

1 Ответ

2 голосов
/ 27 мая 2010

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

Единственное существенное отличие от вашего использования заключается в том, что Notifier использует свои методы apply / update для сохранения стандартного шаблона; вам не нужно каждый раз писать методы def x и def x_ =, но вы должны использовать () для доступа.

...