【デザインパターン】【GoF】【C#】Visitorパターン

Visitorパターン:振る舞いに関するパターン


メリット

* 構造から処理を分離できる
⇒ 既存のオブジェクトに対し、構造を変更せずに、機能を追加できる

使いどころ

* Validation処理(妥当性チェック)
⇒ Validationなら、以下の「Visitorパターンを使うべきケース」での「要素の型(クラス)に応じて、行う処理が異なる」に当てはまる
http://www.aerith.net/design/Visitor-j.html

サンプル

IVisitor.cs (訪れて処理を行う用インターフェイス)

public interface IVisitor
{
    void Visit(Person obj);

    void Visit(BodyInfo obj);
}

Visitor.cs (IVisitorを継承して、具体的な処理を記述)

public class Visitor : IVisitor
{
    public List<string> errorList = new List<string>();

    public void Visit(Person obj)
    {
        errorList.Clear();

        if (obj.id < 0)
        {
            errorList.Add("不正なidです");
        }
        if (obj.age < 0)
        {
            errorList.Add("不正な年齢です");
        }
        if (string.IsNullOrWhiteSpace(obj.name))
        {
            errorList.Add("名前を入力して下さい");
        }
    }

    public void Visit(BodyInfo obj)
    {
        errorList.Clear();

        if (obj.id < 0)
        {
            errorList.Add("不正なidです");
        }
        if (obj.height < 0)
        {
            errorList.Add("不正な身長です");
        }
        if (obj.weight < 0)
        {
            errorList.Add("不正な体重です");
        }
        if (obj.bmi < 0)
        {
            errorList.Add("不正な体重です");
        }
    }
}

IAcceptor.cs (処理を受け入れる用インターフェイス)

public interface IAcceptor
{
    void Accept(IVisitor visitor);
}

Person.cs (IAcceptorを継承して、Visit()をコールする)

public class Person : IAcceptor
{
    public int id;
    public string name;
    public int age;

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

BodyInfo.cs (IAcceptorを継承して、Visit()をコールする)

public class BodyInfo : IAcceptor
{
    public int id;
    public double height;
    public double weight;
    public double bmi;

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

Form1.cs (View)

private void button1_Click(object sender, EventArgs e)
{
    var obj1 = new Person();
    
    int id;
    if(!int.TryParse(this.textBox1.Text,out id))
    {
        this.label1.Text = "error";
    }
    else
    {
        obj1.id = id;
    }

    obj1.name = this.textBox2.Text;

    int age;
    if (!int.TryParse(this.textBox3.Text, out age))
    {
        this.label2.Text = "error";
    }
    else
    {
        obj1.age = age;
    }

    this.Validate(obj1);
}

private void button2_Click(object sender, EventArgs e)
{
    var obj2 = new BodyInfo();

    int id;
    if (!int.TryParse(this.textBox1.Text, out id))
    {
        this.label1.Text = "error";
    }
    else
    {
        obj2.id = id;
    }

    double height;
    if (!double.TryParse(this.textBox2.Text, out height))
    {
        this.label2.Text = "error";
    }
    else
    {
        obj2.height = height;
    }

    double weight;
    if (!double.TryParse(this.textBox3.Text, out weight))
    {
        this.label3.Text = "error";
    }
    else
    {
        obj2.weight = weight;
    }

    double bmi;
    if (!double.TryParse(this.textBox4.Text, out bmi))
    {
        this.label4.Text = "error";
    }
    else
    {
        obj2.bmi = bmi;
    }

    this.Validate(obj2);
}

private void Validate(IAcceptor obj)
{
    Visitor visitor = new Visitor();
    
    obj.Accept(visitor);

    this.label5.Text = string.Empty;
    if (visitor.errorList.Count > 0)
    {
        foreach (var error in visitor.errorList)
        {
            this.label5.Text += error;
            this.label5.Text += "\n";
        }
    }
    else
    {
        this.label5.Text = "OK";
    }
}