SEVENSPIRALS

WPFでMVVMパターン(2) - Model編

プログラミング

というわけで前回に引き続いてMVVMネタです。

今回はModelを作ります。

とりあえずModelから

どういう順番で作るのが正解なのかよく分かりませんが、私はだいたいModelから作り始めます。

理由は単純でModelは絶対必要だから

他の要素、ViewModelだけで無く(GUI的な)Viewも絶対必要とは考えません。この時点ではConsoleアプリケーションから呼び出して使うつもりで作りはじめます。その方がテストが書きやすい(気がする)ので。

だいたい、それ以外の機能はView作ってみて機能が足りなかったらViewModelに押しつけりゃいいというのが私の考えです。(いい加減)

さくっとコーディング

Model作成の時点では特にMVVMパターンだからどうという作法はあまりないので好きに作ります。<おい

というわけでVisual StudioでWPFアプリケーションを作成して下記のクラスを追加しました。

Model.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace MVVM
{
    public class Model : INotifyPropertyChanged
    {
        ObservableCollection<DetailModel> _details;
        public ObservableCollection<DetailModel> Details
        {
            get { return _details; }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public Model()
        {
            _details = new ObservableCollection<DetailModel>();
        }

        public void Order()
        {
            /* なんらかの注文ぽい処理をするのだと思われる */

            foreach (DetailModel detail in _details)
            {
                Console.WriteLine("Key:{0}\\tCode:{1}\\tName:{2}\\tPrice:{3}\\tCount:{4}", detail.Key, detail.Code, detail.Name, detail.Price, detail.Count);
            }
        }
    }
}

DetailModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;

namespace MVVM
{
    public class DetailModel :INotifyPropertyChanged
    {
        public Guid _key;
        public Guid Key
        {
            get
            {
                return _key;
            }
        }

        public string _code;
        public string Code
        {
            get
            {
                return _code;
            }
            set
            {
                if (!string.IsNullOrEmpty(value) && !value.Equals(_code))
                {
                    _code = value;
                    if (PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Code"));
                }
            }
        }

        public string _name;
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                if (!string.IsNullOrEmpty(value) && !value.Equals(_name))
                {
                    _name = value;
                    if (PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Name"));
                }
            }
        }

        public decimal _price;
        public decimal Price
        {
            get
            {
                return _price;
            }
            set
            {
                if (_price != value)
                {
                    _price = value;
                    if (PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Price"));
                }
            }
        }

        public uint _count;
        public uint Count
        {
            get
            {
                return _count;
            }
            set
            {
                if (_count != value)
                {
                    _count = value;
                    if (PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Count"));
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public DetailModel()
        {
            _key = Guid.NewGuid();
            _code = "";
            _name = "";
            _price = 0;
            _count = 0;
        }

        public DetailModel(string code, string name, decimal price, uint count)
        {
            _key = Guid.NewGuid();
            this.Code = code;
            this.Name = name;
            this.Price = price;
            this.Count = count;
        }
    }
}

明細モデル(ただのデータオブジェクトですが)のコレクションと注文の操作を持つModelですけど長いよ・・・。

普通に何も考えずに書くときとの違いは、Modelの操作によってプロパティが更新された際にViewModelに通知する事を考えて、INotifyPropertyChangedを継承していることと同じくコレクションの変更を通知できるようにList<T>ではなく、ObservableCollection<T>を使っているところでしょうか。

ただまあ、このパターンだと操作した結果、値が変わるような事は無さそうなのでアレですが。

Consoleアプリで実行

Model単体では実行できないのでConsoleアプリを作って実行してみます。

ソリューションにConsoleアプリケーションのプロジェクトを追加して、先ほど作ったWPFプロジェクトを参照に追加します。

そこに先ほど作ったModelを呼び出すコードを追加します。こんな感じ。

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var model = new MVVM.Model();

            model.Details.Add(new MVVM.DetailModel("A001", "リンゴ", 100, 1));
            model.Details.Add(new MVVM.DetailModel("A002", "みかん", 80, 3));

            model.Order();

            Console.Read();
        }
    }
}

で、おもむろに実行。出力は以下のようになりました。

Key:e2c9eec2-5b90-4072-a646-c1136ee458b9       Code:A001      Name:リンゴ      Price:100      Count:1
Key:1e28e2fe-9574-4238-bb16-c7013ab698ec       Code:A002      Name:みかん      Price:80       Count:3

まあ、さすがにちゃんと動いてるようですね。

今回は単純なModel&手を抜いてConsoleアプリで実行してますが、実際にはNUnit等でちゃんとしたテストを書いてJenkinsで実行するといいと思います。というかしましょう。私含めて。<やってないのかよ

特にリグレッションテストってある日突然情熱に目覚めて、やろう。と思ってもいざやると機能を追加する度にやることが加速度的に増えて心が折れがちです。

なのでロジックの部分をあらかじめテストしやすいように作るのが大事なんだと思います。


というところで今回はここまで。次回はViewModel・・・をすっ飛ばしてViewを作ろうと思います。