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

Visitorパターン

デザインパターンの中のVisitorパターンは、

  • データ構造をたどって、その要素について処理をし、かつ、
  • その処理が複数ある。

という場合に使う。ここでは、データ構造として以下のようなComponentクラス, Compositeクラス, およびLeafクラスを使った木構造を例として使う。

  • Componentは抽象クラス。
    • Accept()でVisitorを受け入れる。
  • Compositeは子Componentを持つComponent。Accept()は、
    • VisitorのVisitComposite()を呼び、VisitorにCompositeの処理を実施させる。
    • それぞれの子ComponentのAccept()を呼び、Visitorにデータ構造を巡回させる。
  • Leafは末端要素のComponent。Accept()は、
    • VisitorのVisitLeaf()を呼び、VisitorにLeafの処理を実施させる。
abstract class Component
{
    private readonly string m_name;
    private readonly int m_size;

    protected Component(string name, int size)
    {
	m_name = name;
	m_size = size;
    }

    internal string Name { get { return m_name; } }
    internal int Size { get { return m_size; } }

    internal abstract void Accept(Visitor visitor);
}

class Composite : Component
{
    private readonly Component[] m_childComponents;

    internal Composite(string name, int size,
                       params Component[] childComponents)
	: base(name, size)
    {
	m_childComponents = childComponents;
    }

    internal override void Accept(Visitor visitor)
    {
	visitor.VisitComposite(this);
	foreach (Component childComponent in m_childComponents)
	{
	    childComponent.Accept(visitor);
	}
    }
}

class Leaf : Component
{
    internal Leaf(string name, int size)
	: base(name, size)
    { }

    internal override void Accept(Visitor visitor)
    {
	visitor.VisitLeaf(this);
    }
}

Visitorクラスは、データ構造のそれぞれの要素の処理に必要なメソッドを定義する。Visitorの派生クラスは、それぞれの要素で呼び出されるメソッドで実際に行う処理を記述する。

この例のVisitorクラスでは、以下のメソッドを定義する。

  • Composite要素に対して呼び出される VisitComposite()メソッド
  • Leaf要素に対して呼び出される VisitLeaf()メソッド

Visitorの派生クラスとして、以下の2種類を作成する。

  • それぞれのComponentの名前を表示するPrintNameVisitor。
  • CompositeとLeafのそれぞれの合計サイズを計算するTotalSizeCalculationVisitor。
class Visitor
{
    internal virtual void VisitComposite(Composite composite) { }
    internal virtual void VisitLeaf(Leaf leaf) { }
}

class PrintNameVisitor : Visitor
{
    internal override void VisitComposite(Composite composite)
    {
	Console.WriteLine("Composite: " + composite.Name);
    }

    internal override void VisitLeaf(Leaf leaf)
    {
	Console.WriteLine("Leaf: " + leaf.Name);
    }
}

class TotalSizeCalculationVisitor : Visitor
{
    private int m_compositeTotalSize;
    private int m_leafTotalSize;

    internal override void VisitComposite(Composite composite)
    {
	m_compositeTotalSize += composite.Size;
    }

    internal override void VisitLeaf(Leaf leaf)
    {
	m_leafTotalSize += leaf.Size;
    }

    internal void PrintTotalSize()
    {
	Console.WriteLine("Composite total size: {0}", m_compositeTotalSize);
	Console.WriteLine("Leaf total size: {0}", m_leafTotalSize);
    }
}

これらを使うプログラムは、以下のようになる。

class Program
{
    static void Main(string[] args)
    {
	// データ構造を作成する。
	var components = new Composite("コンポジット-親", 100,
				       new Leaf("葉-親-1", 1),
				       new Composite("コンポジット-子", 30,
						     new Leaf("葉-子", 2)),
				       new Leaf("葉-親-2", 3));

	// PrintNameVisitor で、それぞれの Component 名を表示する
	var printNameVisitor = new PrintNameVisitor();
	components.Accept(printNameVisitor);

	// TotalSizeCalculationVisitor で合計サイズを計算する。
	var totalSizeCalculationVisitor = new TotalSizeCalculationVisitor();
	components.Accept(totalSizeCalculationVisitor);
	totalSizeCalculationVisitor.PrintTotalSize();

	// ===== 画面への出力 =====
	// Composite: コンポジット-親
	// Leaf: 葉-親-1
	// Composite: コンポジット-子
	// Leaf: 葉-子
	// Leaf: 葉-親-2
	// Composite total size: 130
	// Leaf total size: 6
    }
}

Visitorパターンにより、

  • データ構造をたどる処理
  • データ構造中の要素についての処理

の2つが分離された。このことにより、

  • データ構造をたどる処理は、要素についての処理が複数あっても、1回だけ記述すればよい。
  • 要素について新しい処理が必要ならば、新しいVisitorの派生クラスを作成すればよい。

となる。