Scalaの慣用プロパティが通知を変更しましたか?
質問
WPF/Silverlightデータバインディング、つまりInotifyPropertyChangedの実装でデータバインディングを使用して、よりクリーンな代替品(Scalaに慣用的な)を見つけようとしています。まず、背景:
.NET WPFまたはSilverLightアプリケーションでは、双方向データバインディングの概念があります(つまり、UIの一部の要素の値を、UI要素に変更するような方法でDatAcontextの.NETプロパティにバインドするプロパティに影響を与え、その逆です。これを有効にする1つの方法は、DatAcontextにInotifyPropertyChangedインターフェイスを実装することです。残念ながら、これは「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)=>ユニットのコールバックのリストを管理する特性であり、OnpropertyChangedはすべてのコールバックを呼び起こし、anyRefのように「このコールバック」を渡す方法であると仮定しましょう。 、および渡された文字列)。これは、C#のイベントにすぎません。
すぐに問題を見ることができます。これは、2つのプロパティだけのボイラープレートコードのトンです。私はいつもこのようなものを代わりに書きたいと思っていました:
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_ =メソッドを持っている場合、私はこのように簡単に書くことができることを知っています(これは私が現在使用している方法です):
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のInclicitsまたはその他の機能/イディオムを使用して最初のバージョンを実装する方法はありますか?私が考えることができる他の唯一のことはこのようなことです。これは上記の2つのバージョンのいずれよりも冗長ですが、元のバージョンよりも冗長ではありません。
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
}
}
あなたが望むものとほぼ同等でなければなりません。 (繰り返しますが、これがどれほど柔軟であるかはわかりません。シンボルのセットを作成できます。ドローオーダーシンボル。)
使用量との唯一の大きな違いは、紹介者が適用/更新方法を使用してボイラープレートを保存することです。毎回def xとdef x_ =メソッドを記述する必要はありませんが、アクセスに()を使用する必要があります。