Rさんのところで紹介されていた、面白げな記事。
スマートなアプリケーションアーキテクチャの構築(1)
その後の、Rさんの記事も見たりして、自分ならこうするかな~というのを書いてみました。
Property<T>クラスは抽象クラス。
/// <summary>
/// プロパティを表すクラス。
/// Valueでプロパティの値を取得及び書き込み、Errorでプロパティのエラーを取得及び書き込みできます。
/// </summary>
/// <typeparam name="T">プロパティの型</typeparam>
public abstract class Property<T> : INotifyPropertyChanged
{
/// <summary>
/// プロパティの値を取得または設定する。
/// </summary>
public T Value
{
get
{
return GetValue();
}
set
{
SetValue(value);
OnPropertyChanged("Value");
}
}
/// <summary>
/// プロパティの値を取得する処理をします。
/// </summary>
/// <returns></returns>
protected abstract T GetValue();
/// <summary>
/// プロパティに値を設定する処理をします。
/// </summary>
/// <param name="value">設定する値</param>
protected abstract void SetValue(T value);
private object _error;
/// <summary>
/// プロパティのエラー情報を取得または設定します。
/// </summary>
public object Error
{
get
{
return _error;
}
set
{
_error = value;
OnPropertyChanged("Error");
}
}
#region INotifyPropertyChanged メンバ
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(params string[] names)
{
var h = PropertyChanged;
if (h != null)
{
foreach (var name in names)
{
h(this, new PropertyChangedEventArgs(name));
}
}
}
#endregion
}
そして、純粋に値を保持してくれるFieldProperty<T>クラス。
/// <summary>
/// 単純にフィールドの値を取得または設定するプロパティ
/// </summary>
/// <typeparam name="T"></typeparam>
public class FieldProperty<T> : Property<T>
{
private T _value;
protected override T GetValue()
{
return _value;
}
protected override void SetValue(T value)
{
_value = value;
}
}
次に、Delegateで値の取得と設定ロジックを好きに入れ替えれるDelegateProperty<T>クラス。
/// <summary>
/// Delegateで値の取得または設定ロジックを入れ替えれるプロパティ
/// </summary>
/// <typeparam name="T"></typeparam>
public class DelegateProperty<T> : Property<T>
{
private Func<T> _get;
private Action<T> _set;
public DelegateProperty(Func<T> get, Action<T> set)
{
_get = get;
_set = set;
}
protected override T GetValue()
{
return _get();
}
protected override void SetValue(T value)
{
_set(value);
}
}
値の妥当性検証を行うValidationProperty<T>クラス
/// <summary>
/// 値の検証をプロパティに付加する。
/// </summary>
/// <typeparam name="T"></typeparam>
public class ValidationProperty<T> : Property<T>
{
private Property<T> _property;
private ValidationRule[] _validators;
/// <summary>
/// プロパティと妥当性検証ロジックを設定するコンストラクタ
/// </summary>
/// <param name="property"></param>
/// <param name="validators"></param>
public ValidationProperty(Property<T> property,
params ValidationRule[] validators)
{
_property = property;
_validators = validators;
}
protected override T GetValue()
{
return _property.Value;
}
protected override void SetValue(T value)
{
_property.Value = value;
// 値の検証
var result = Validate();
Error = result.ErrorContent;
}
/// <summary>
/// 値の検証を行う。
/// </summary>
/// <returns></returns>
private ValidationResult Validate()
{
foreach (var validator in _validators)
{
var result = validator.Validate(_property.Value,
Thread.CurrentThread.CurrentCulture);
if (!result.IsValid)
{
return result;
}
}
return ValidationResult.ValidResult;
}
}
そして、最後に、これらのクラスを作成するときのヘルパークラス。
public static class Props
{
public static Property<T> Valid<T>(Property<T> property,
params ValidationRule[] validators)
{
return new ValidationProperty<T>(
property, validators);
}
public static Property<T> Deleg<T>(Func<T> get, Action<T> set)
{
return new DelegateProperty<T>(get, set);
}
public static Property<T> Prop<T>()
{
return new FieldProperty<T>();
}
}
使ってみよう
ということで、早速使ってみます。ViewModelの前にModelとなるクラスを作成します。
public class Person
{
public string Name { get; set; }
}
そして、それをラップするViewModelクラスを作成します。
public class PersonViewModel
{
private Person _model;
public PersonViewModel (Person model)
{
_model = model;
// 単純なプロパティ
this.ID = Props.Prop<int?>();
// _modelフィールドの値を取得または設定する上に必須入力の検証も行うプロパティ
this.Name = Props.Valid(
Props.Deleg(() => _model.Name, (value) => _model.Name = value),
new RequiredValidationRule("必須入力項目です"));
}
public Property<int?> ID { get; private set; }
public Property<string> Name { get; private set; }
}
コメントにもありますが、IDプロパティが超単純なプロパティです。
Nameプロパティは、PersonクラスのNameプロパティと連動します。
すっきりかけていいかもしれん。
このViewModelを表示するためのView(XAML)もさくっと準備してみました。
<Window x:Class="WpfPropDef.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfPropDef"
Title="Window1">
<StackPanel>
<TextBox Text="{Binding ID.Value}" />
<TextBox Text="{Binding Name.Value}" />
<TextBlock Text="{Binding Name.Error}" />
<Button Content="Name Set!!" Click="NameSet" />
<Button Content="Alert" Click="Alert" />
</StackPanel>
</Window>
DataContextに、さっき作ったViewModelを突っ込むようにコンストラクタあたりに以下のコードを追加します。ついでに、ボタンクリック処理も。
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new PersonViewModel(new Person { Name = "太郎" });
}
private void NameSet(object sender, RoutedEventArgs e)
{
var model = GetModel();
model.Name.Value = "はろう そねお";
}
private PersonViewModel GetModel()
{
return DataContext as PersonViewModel;
}
private void Alert(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Format("{0}: {1}",
GetModel().ID.Value, GetModel().Name.Value));
}
}
今までViewModelの基本クラスに一生懸命実装してきたことが、Property<T>関連クラスに集約されていい感じになったのだろうか。
今回のように継承より委譲のほうが後々つぶしが効くかな?