访问者模式 - Visitor Pattern
定义
提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
设计的原则和思想
- 对象(稳定的数据结构)与操作(易变的操作)解耦
- 不变部分是对象的数据结构,变化部分是操作。
- 核心思想是同样一件事,不同的人有不同的做法。
一句话概括设计模式
通过访问者去操作对象的行为。
结构中包含的角色
- Vistor(抽象访问者)
- ConcreteVisitor(具体访问者)
- Element(抽象元素)
- ConcreteElement(具体元素)
- ObjectStructure(对象结构)
最小可表达代码
// 抽象访问者
abstract class Visitor
{
public abstract function visit(Element $element);
}
// 具体访问者
class ConcreteVisitor extends Visitor
{
public function visit(Element $element)
{
echo "访问者具体业务";
}
}
// 抽象元素
interface Element
{
public function accept(Visitor $visitor);
}
// 具体元素
class ConcreteElement implements Element
{
public function accept(Visitor $visitor)
{
$visitor->visit($this);
}
}
// 对象结构
class ObjectStructure
{
private $elements = [];
public function add(Element $element)
{
$this->elements[] = $element;
}
public function accept(Visitor $visitor)
{
foreach ($this->elements as $element) {
$element->accept($visitor);
}
}
}
$visitor = new ConcreteVisitor();
$elementA = new ConcreteElement();
$elementB = new ConcreteElement();
$objectStructure = new ObjectStructure();
$objectStructure->add($elementA);
$objectStructure->add($elementB);
$objectStructure->accept($visitor);
优点
- 增加新的访问者无须修改原有系统。
- 相同的对象结构可以供多个不同的访问者访问。
- 将有关元素对象的访问行为集中到一个访问者对象中。
缺点
- 增加新的元素类要在访问者中增加一个新的操作,并在每一个具体访问者类中增加相应的具体操作。
- 具体元素对访问者公布细节,违反了迪米特原则。
何时使用
- 在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
- 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
- 对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
实际应用场景
- 记者采访。
- 医院付药费。计价员和药房工作者作为访问者,药品作为访问元素、处方单作为对象结构。
- 某公司人力资源部负责汇总每周员工工作时间,而财务部负责计算每周员工工资。
- 艺术公司用纸画出图画。造币公司用纸可以印出纸币。
- 评定工程师一年的工作绩效。CTO关注工程师的代码量,CEO关注的是工程师的KPI。
- 文件遍历。目录和文件是访问者,文件树节点是访问元素。
- 报表。不同部门对同样的报表有不同的需求。