読者です 読者をやめる 読者になる 読者になる

プログラミング C# - 翔ソフトウェア (Sho's)

C#/.NET/ソフトウェア開発など

C# による Observer パターンの実装 その3 - 複数のプロパティの更新イベントをフレームワーク側で振り分け

.NET C# Design Pattern
Observer

前回「C# による Observer パターンの実装 その2 - event による実装」と云う記事で、Observer パターンの C# による実装の第二回として、C# の event を用いた実装を行ってみた。

event を用いることで、より C# らしい実装となった。

C# での Observer パターンの実装 1 - 古典的な実装」のフレームワーク部にあたる部分を C# の event 機構で行ってしまっている訳だ。

すっきりとした実装となったが、監視される側 (Observer 側) でプロパティ毎にイベントを用意しなければいけない、という面があった。

「プロパティの数が増えてくると、コードが複雑になりそう」と云う点が気になった。

そこで、今回は、「C# による Observer パターンの実装 その3 - 複数のプロパティの更新イベントをフレームワーク側で振り分け」として、その2で無くなったフレームワーク部を復活させ、そちらに複数のイベント処理を任せてみる。

C# での Observer パターンの実装 3

・クラス図 1

今回のクラス図はこんな感じだ。

「C# での Observer パターンの実装 3」のクラス図
C# での Observer パターンの実装 3」のクラス図

C# による Observer パターンの実装 その1」のときと同様、上半分の青い部分がフレームワーク部、下半分の赤い部分がアプリケーション部だ。

フレームワーク部 (抽象部)

先ずは、フレームワーク部の方から。

Observable (更新を監視される側) は、今回は、一つだけの Update というイベントを持つ。
どのプロパティが更新されてもこのイベントを起こす。

但し、プロパティ名を引数で受け取るようになっている。

RaiseUpdate と云うメソッドに文字列でプロパティ名を渡すことで、Update イベントが起きる。

また、Observer (更新を監視する側) は、 DataSource として Observable が渡されると、Observable の Update イベントにイベント ハンドラーとして Update メソッドを設定する。

AddUpdateAction は、プロパティの名称毎に処理を登録するメソッドだ。

Observable 側で更新イベントが起きてイベント ハンドラーの Update が呼ばれると、プロパティの名毎の処理が呼ばれる仕組みだ。

最後に、ObjectExtensions だが、このクラスは、オブジェクトのプロパティ名からプロパティの値を取り出すために使われるメソッド Eval を持つ。
このメソッド Eval は、Observer の中で、プロパティ名からプロパティの値を取り出すために使われる。

・アプリケーション部 (具象部)

Employee は Observable だ。
今回も Model として Number と Name と云う二つのプロパティを持っている。

Update イベントは、ここには無い。

各プロパティが更新されたときに、RaiseUpdate をプロパティ名を引数にして呼ぶことで、ベース クラス Observable の持つ Update イベントを起こす。

EmployeeView はオブザーバーだ。

今回も、EmployeeView は Employee を表示するための View で、AddUpdateAction メソッドを使って、プロパティ毎の更新処理を登録する。

・実装

それでは、実装していこう (一部エラー処理を省略)。

フレームワーク部の実装

フレームワーク部の ObjectExtensions と Observable、Observer から実装しよう。

// C# による Oberver パターンの実装 その3
using System;
using System.Collections.Generic;

// フレームワーク部

public static class ObjectExtensions
{
    // オブジェクトの指定された名前のプロパティの値を取得
    public static object Eval(this object item, string propertyName)
    {
        var propertyInfo = item.GetType().GetProperty(propertyName);
        return propertyInfo == null ? null : propertyInfo.GetValue(item, null);
    }
}

abstract class Observable // 更新を監視される側
{
    public event Action<string> Update;

    protected void RaiseUpdate(string propertyName)
    {
        if (Update != null)
            Update(propertyName);
    }
}

abstract class Observer // 更新を監視する側
{
    Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>();
    Observable dataSource = null;

    public Observable DataSource
    {
        set {
            dataSource = value;
            value.Update += Update;
        }
    }

    protected void AddUpdateAction(string propertyName, Action<object> updateAction)
    {
        updateExpressions[propertyName] = updateAction;
    }

    void Update(string propertyName)
    {
        Action<object> updateAction;
        if (updateExpressions.TryGetValue(propertyName, out updateAction))
            updateAction(dataSource.Eval(propertyName));
    }
}
・アプリケーション部の実装

続いて、アプリケーション部。Employee と EmployeeView、TextControl、そして Main を含んだクラス Program だ。

// C# による Oberver パターンの実装 その3
using System;

// アプリケーション部

// Model
class Employee : Observable
{
    int number = 0;
    string name = string.Empty;

    public int Number
    {
        get { return number; }
        set {
            if (value != number) {
                number = value;
                RaiseUpdate("Number");
            }
        }
    }

    public string Name
    {
        get { return name; }
        set {
            if (value != name) {
                name = value;
                RaiseUpdate("Name");
            }
        }
    }
}

class TextControl // テキスト表示用のUI部品 (ダミー)
{
    public string Text
    {
        set { Console.WriteLine("TextControl is updated: {0}", value); }
    }
}

class EmployeeView : Observer // Employee 用の View
{
    TextControl numberTextControl = new TextControl(); // Number 表示用
    TextControl nameTextControl = new TextControl(); // Name 表示用

    public EmployeeView()
    {
        AddUpdateAction("Number", number => numberTextControl.Text = *1;
        AddUpdateAction("Name", name => nameTextControl.Text = (string)name);
    }
}

class Program
{
    static void Main()
    {
        var employee = new Employee(); // Model, Observable
        var employeeView = new EmployeeView(); // View, Observer

        employeeView.DataSource = employee; // データバインド
        employee.Number = 100; // Number を変更。employeeView に反映されたかな?
        employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
    }
}
・クラス図 2

先のクラス図に、引数と戻り値の型を書き加えたものを示す。

「C# での Observer パターンの実装 3」のクラス図
C# での Observer パターンの実装 3」のクラス図
・実行結果

実行してみると、次のようになる。

前回の二つ目と同じ結果だ。

TextControl is updated: 100
TextControl is updated: 福井太郎

Number と Name それぞれの更新で、それぞれの TextControl が更新されている。

■ 今回のまとめと次回の予告

今回は、フレームワーク部で、プロパティ毎の更新処理が呼ばれるようにした。

結果として、アプリケーション部の Employee でプロパティ毎に更新イベントを用意する必要がなくなった。

しかし、アプリケーション部の一部に文字列でプロパティを指定する部分が出来てしまった。

Employee の中の、RaiseUpdate("Number") と RaiseUpdate("Name")、EmployeeView の中の AddUpdateAction("Number", ...) と AddUpdateAction("Name", ...) だ。

次回は、この問題を解決しよう。

*1:int)number).ToString(