🧩 Visitor パターン
✅ 設計意図
- 「処理を適用する側(訪問者)」と「データ構造」を分離
- データ構造に変更を加えずに処理だけを追加できる
✅ 適用理由
- 複数のデータ型や出力形式を持つ対象に対して、異なる操作を外部から適用したい
- 出力方式とデータ処理を切り離す
✅ 向いているシーン
- データ構造は安定していて、処理ロジックだけ追加・変更される場合
✅ コード例
- TypeScript
- PHP
- Python
// 要素インターフェース
interface Exportable {
accept(visitor: ExportVisitor): void;
}
// Visitor インターフェース
interface ExportVisitor {
visitPdf(pdf: PdfExporter): void;
visitCsv(csv: CsvExporter): void;
visitXml(xml: XmlExporter): void;
}
// 各要素(出力形式)
class PdfExporter implements Exportable {
constructor(private data: string) {}
export(): void {
console.log(`PDF出力: ${this.data}`);
}
accept(visitor: ExportVisitor): void {
visitor.visitPdf(this);
}
}
class CsvExporter implements Exportable {
constructor(private data: string) {}
export(): void {
console.log(`CSV出力: ${this.data}`);
}
accept(visitor: ExportVisitor): void {
visitor.visitCsv(this);
}
}
class XmlExporter implements Exportable {
constructor(private data: string) {}
export(): void {
console.log(`XML出力: ${this.data}`);
}
accept(visitor: ExportVisitor): void {
visitor.visitXml(this);
}
}
// Visitor 実装:ログ付き出力
class LoggingVisitor implements ExportVisitor {
visitPdf(pdf: PdfExporter): void {
console.log("開始ログ");
pdf.export();
console.log("完了ログ");
}
visitCsv(csv: CsvExporter): void {
console.log("開始ログ");
csv.export();
console.log("完了ログ");
}
visitXml(xml: XmlExporter): void {
console.log("開始ログ");
xml.export();
console.log("完了ログ");
}
}
// 利用例
const visitor = new LoggingVisitor();
const pdf = new PdfExporter("帳票データ");
pdf.accept(visitor);
const csv = new CsvExporter("ユーザー一覧");
csv.accept(visitor);
<?php
interface ExportVisitor {
public function visitPdf(PdfExporter $pdf): void;
public function visitCsv(CsvExporter $csv): void;
public function visitXml(XmlExporter $xml): void;
}
interface Exportable {
public function accept(ExportVisitor $visitor): void;
}
class PdfExporter implements Exportable {
public function __construct(private string $data) {}
public function export(): void {
echo "PDF出力: {$this->data}\n";
}
public function accept(ExportVisitor $visitor): void {
$visitor->visitPdf($this);
}
}
class CsvExporter implements Exportable {
public function __construct(private string $data) {}
public function export(): void {
echo "CSV出力: {$this->data}\n";
}
public function accept(ExportVisitor $visitor): void {
$visitor->visitCsv($this);
}
}
class XmlExporter implements Exportable {
public function __construct(private string $data) {}
public function export(): void {
echo "XML出力: {$this->data}\n";
}
public function accept(ExportVisitor $visitor): void {
$visitor->visitXml($this);
}
}
class LoggingVisitor implements ExportVisitor {
public function visitPdf(PdfExporter $pdf): void {
echo "開始ログ\n";
$pdf->export();
echo "完了ログ\n";
}
public function visitCsv(CsvExporter $csv): void {
echo "開始ログ\n";
$csv->export();
echo "完了ログ\n";
}
public function visitXml(XmlExporter $xml): void {
echo "開始ログ\n";
$xml->export();
echo "完了ログ\n";
}
}
// 利用例
$visitor = new LoggingVisitor();
$pdf = new PdfExporter("帳票データ");
$pdf->accept($visitor);
$csv = new CsvExporter("ユーザー一覧");
$csv->accept($visitor);
from abc import ABC, abstractmethod
# Visitor インターフェース
class ExportVisitor(ABC):
@abstractmethod
def visit_pdf(self, pdf): pass
@abstractmethod
def visit_csv(self, csv): pass
@abstractmethod
def visit_xml(self, xml): pass
# Exportable インターフェース
class Exportable(ABC):
@abstractmethod
def accept(self, visitor: ExportVisitor): pass
# 出力形式
class PdfExporter(Exportable):
def __init__(self, data: str):
self.data = data
def export(self):
print(f"PDF出力: {self.data}")
def accept(self, visitor: ExportVisitor):
visitor.visit_pdf(self)
class CsvExporter(Exportable):
def __init__(self, data: str):
self.data = data
def export(self):
print(f"CSV出力: {self.data}")
def accept(self, visitor: ExportVisitor):
visitor.visit_csv(self)
class XmlExporter(Exportable):
def __init__(self, data: str):
self.data = data
def export(self):
print(f"XML出力: {self.data}")
def accept(self, visitor: ExportVisitor):
visitor.visit_xml(self)
# Visitor 実装
class LoggingVisitor(ExportVisitor):
def visit_pdf(self, pdf: PdfExporter):
print("開始ログ")
pdf.export()
print("完了ログ")
def visit_csv(self, csv: CsvExporter):
print("開始ログ")
csv.export()
print("完了ログ")
def visit_xml(self, xml: XmlExporter):
print("開始ログ")
xml.export()
print("完了ログ")
# 利用例
visitor = LoggingVisitor()
pdf = PdfExporter("帳票データ")
pdf.accept(visitor)
csv = CsvExporter("ユーザー一覧")
csv.accept(visitor)
✅ 解説
このコードは Visitor
パターン を使用して、異なる要素(PdfExporter
, CsvExporter
, XmlExporter
)に対して
共通の操作(ログ付き出力)を追加する設計を実現している。
Visitor
パターンは、オブジェクト構造を変更せずに、新しい操作を追加するデザインパターンであり、要素ごとに異なる処理を実行する場合に有効。
1. Visitor パターンの概要
- Element:
Visitor
を受け入れるインターフェースを定義- このコードでは
Exportable
が該当
- このコードでは
- ConcreteElement:
Element
を実装し、Visitor
を受け入れる具体的な要素- このコードでは
PdfExporter
,CsvExporter
,XmlExporter
が該当
- このコードでは
- Visitor: 要素ごとの操作を定義するインターフェース
- このコードでは
ExportVisitor
が該当
- このコードでは
- ConcreteVisitor:
Visitor
を実装し、要素ごとの具体的な操作を提供- このコードでは
LoggingVisitor
が該当
- このコードでは
2. 主なクラスとその役割
Exportable
- 要素の共通インターフェース
accept(visitor: ExportVisitor): void
メソッドを定義し、Visitor
を受け入れる
PdfExporter
,CsvExporter
,XmlExporter
Exportable
を実装した具体的な要素- 各クラスでデータを特定の形式(PDF, CSV, XML)で出力
accept
メソッドでVisitor
を受け入れ、対応するメソッド(visitPdf
,visitCsv
,visitXml
)を呼び出す
ExportVisitor
Visitor
の共通インターフェース- 各要素に対応するメソッド(
visitPdf
,visitCsv
,visitXml
)を定義
LoggingVisitor
Visitor
の具体的な実装- 各要素に対してログ付きの出力処理を実行
3. UML クラス図
4. Visitor パターンの利点
- 新しい操作の追加が容易: 要素のクラスを変更せずに、新しい操作(
Visitor
)を追加可能 - 要素ごとの処理を分離: 要素ごとの処理を
Visitor
にまとめることで、コードの保守性が向上 - オープン/クローズド原則: 要素の構造を変更せずに、新しい操作を追加可能
この設計は、異なる要素に対して共通の操作を実行する必要がある場面で非常に有効であり、コードの拡張性と保守性を向上させる。