たくあんポリポリ

勉強したことを載せていきます。最近、技術系の記事はZennに書いています。(https://zenn.dev/chittai)

【C#】Visitorパターンについて調べてみる

デザインパターンの一つ、Visitorパターンについて勉強してみました。

ja.wikipedia.org

Visitorパターンとは何か

Visitor パターンは、オブジェクト指向プログラミング およびソフトウェア工学 において、 アルゴリズムをオブジェクトの構造から分離するためのデザインパターンである。分離による実用的な結果として、既存のオブジェクトに対する新たな操作を構造を変更せずに追加することができる。

Wikipediaでは上記のように書かれています。調べてみたのも含めてまとめると、ここでいうオブジェクトというのはデータ構造の話で、データ構造と処理の部分を分けてしまおうという思想です。たとえば、ある大きなデータ構造でいろんなレポートを作成するジェネレータを作成する場合にVisitorパターンを使うと、データ構造自体は生成用のコードを保持せずにすみ、好きなだけレポートを作成することができます。

クラスの役割と処理の流れ

クラス図

Wikipediaより。

f:id:c_taquna:20190323032206j:plain:w300

Visitorはインタフェースで、visitメソッドを持ちます。処理を記述するクラスで継承します。Visitorはデータ構造に依存していて、データ構造の種類分visitを記述します。ConcreteVisitorで、実際にデータが渡された時にどのような処理をするのか記述します。

Elementはacceptsメソッドを持ちます。これはデータ構造のクラスで継承します。具象クラスにて、acceptsの具体的な処理について実装します。

細かいことを書きましたが、とりあえず実装してみたので、それを使って説明します。

実装

USERIDとPASSWORDでUSERIDは英字のみ、PASSWORDは英数字のみというValidationCheckを行っています。

クラス図

f:id:c_taquna:20190323125115j:plain:w300

全体

interface IVisitor
{
    void visit(AuthenticationInformation ai);
}

class ValidationCheck : IVisitor
{
    public void visit(AuthenticationInformation ai)
    {
        UserIdValidationCheck(ai);
        PasswordValidationCheck(ai);
    }
    private void UserIdValidationCheck(AuthenticationInformation ai)
    {
        var isAlphabetOnly = !Regex.IsMatch(ai.userId, @"[^a-zA-Z]");
        if (!isAlphabetOnly)
        {
            Console.WriteLine("USER ID に半角英字以外が含まれています。");
            return;
        }
        Console.WriteLine("USER ID の使用は問題ありません");
    }
    private void PasswordValidationCheck(AuthenticationInformation ai)
    {
        var isAlphanumericOnly = !Regex.IsMatch(ai.userPassword, @"[^a-zA-Z0-9]");
        if (!isAlphanumericOnly)
        {
            Console.WriteLine("PASSWORD に半角英数字以外が含まれています。");
            return;
        }
        Console.WriteLine("PASSWORD の使用は問題ありません");
    }
}

abstract class UserData
{
    public string userId;
    public string userPassword;
    public abstract void accepts(IVisitor visitor);
}

class AuthenticationInformation : UserData
{
    public override void accepts(IVisitor visitor)
    {
        visitor.visit(this);
    }
}

class Program
{
    static void Main(string[] args)
    {
        UserData ai = new AuthenticationInformation();
        ai.userId = "chittai1";
        ai.userPassword = "test1@[]";
        var vc = new ValidationCheck();
        ai.accepts(vc);
    }
}

処理の流れ

こういうときは、処理の流れを見たほうがいいと思っているので説明します。まず、一番最初。Programクラスで呼び出しが行われます。

定義したデータ構造にデータを格納し、ValidationCheckのインスタンスを引数に、acceptsメソッドを呼び出しています。

UserData ai = new AuthenticationInformation(); 
ai.userId = "chittai1";
ai.userPassword = "test1@[]";
var vc = new ValidationCheck();
ai.accepts(vc);

そうすると、次はAuthenticationInformationで実装した下記の処理が呼び出されます。ここで書かれているvisitorは先程のValidationCheckのインスタンスです。つまり、ValidationCheckで実装されているvisitメソッドが呼び出されています。this、つまりこのクラスのデータ構造クラスのインスタンスを引数にしています。

visitor.visit(this);

visitの処理は書かれているとおりです。見てもらえれば分かると思いますが、渡されたデータのチェックをしています。

public void visit(AuthenticationInformation ai)
{
    UserIdValidationCheck(ai);
    PasswordValidationCheck(ai);
}

これで、想定通りに処理がされます。新しいデータ構造があっても、追加すればすみそうです。

感想

なんかとりとめのない記事になってしまったが、今回はここまで。