ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## **模式的定义与特点** 访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。 **优点** 1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。 2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。 3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。 4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。 5. 将相关的事物集中到一个访问者对象中。 **缺点** 1. 增加新的元素类(新数据结构)很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。 2. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。 3. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。 封装某些用于作用于某种数据结构中各元素的操作,可以在不改变数 据结构的前提下定义作用于这些元素的新操作。如银行排号机。 ## **应用场景** 如:排队,排号。 1. 对象结构相对稳定,但其操作算法经常变化的程序。 2. 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。 3. 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。 思路:将类看做一个对象结构,然后划分元素比较元素是否个数稳定 ## **模式的结构** 1. 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。 2. 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。 3. 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。 4. 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。 5. 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。 其结构图如图 ![](https://img.kancloud.cn/25/1b/251bb73fa79db4365546555c909ab567_750x665.png) ``` <?php //抽象访问者 interface Visitor { public function visit(Element $element); } //具体访问者A类 class ConcreteVisitorA implements Visitor { public function visit(Element $element) { if ($element instanceof ConcreteElementA) { print_r("具体访问者A访问-->" . $element->operationA()); } else if ($element instanceof ConcreteElementB) { print_r("具体访问者A访问-->" . $element->operationB()); } } } //具体访问者B类 class ConcreteVisitorB implements Visitor { public function visit(Element $element) { if ($element instanceof ConcreteElementA) { print_r("具体访问者B访问-->" . $element->operationA()); } else if ($element instanceof ConcreteElementB) { print_r("具体访问者B访问-->" . $element->operationB()); } } } //抽象元素类 interface Element { public function accept(Visitor $visitor); } //具体元素A类 class ConcreteElementA implements Element { public function accept(Visitor $visitor) { $visitor->visit($this); } public function operationA() { return "具体元素A的操作。"; } } //具体元素B类 class ConcreteElementB implements Element { public function accept(Visitor $visitor) { $visitor->visit($this); } public function operationB() { return "具体元素B的操作。"; } } //对象结构角色 class ObjectStructure { private $list = array(); public function accept(Visitor $visitor) { foreach ($this->list as $key => $element) { $element->accept($visitor); } } public function add(Element $element) { $this->list[] = $element; } public function remove(Element $element, $changeIndex = false) { if ($changeIndex === true) { $key = array_search($element, $this->list); if ($key !== false) { array_splice($this->list, $key, 1); } } else { $this->list[$element]; foreach ($this->list as $key => $value) { if ($value === $element) { unset($this->list[$key]); } } } } } class VisitorPattern { public static function main() { //实例化对象结构角色 $os = new ObjectStructure(); $os->add(new ConcreteElementA()); $os->add(new ConcreteElementB()); //实例化具体访问者A类 $visitor = new ConcreteVisitorA(); $os->accept($visitor); print_r("------------------------"); //实例化具体访问者B类 $visitor = new ConcreteVisitorB(); $os->accept($visitor); } } VisitorPattern::main(); ``` 简单例子: ~~~ interface Visitor { // 抽象访问者角色 public function visitConcreteElementA(ConcreteElementA $elementA); public function visitConcreteElementB(concreteElementB $elementB); } interface Element { // 抽象节点角色 public function accept(Visitor $visitor); } class ConcreteVisitor1 implements Visitor { // 具体的访问者1 public function visitConcreteElementA(ConcreteElementA $elementA) {} public function visitConcreteElementB(ConcreteElementB $elementB) {} } class ConcreteVisitor2 implements Visitor { // 具体的访问者2 public function visitConcreteElementA(ConcreteElementA $elementA) {} public function visitConcreteElementB(ConcreteElementB $elementB) {} } class ConcreteElementA implements Element { // 具体元素A private $_name; public function __construct($name) { $this->_name = $name; } public function getName() { return $this->_name; } public function accept(Visitor $visitor) { // 接受访问者调用它针对该元素的新方法 $visitor->visitConcreteElementA($this); } } class ConcreteElementB implements Element { // 具体元素B private $_name; public function __construct($name) { $this->_name = $name;} public function getName() { return $this->_name; } public function accept(Visitor $visitor) { // 接受访问者调用它针对该元素的新方法 $visitor->visitConcreteElementB($this); } } class ObjectStructure { // 对象结构 即元素的集合 private $_collection; public function __construct() { $this->_collection = array(); } public function attach(Element $element) { return array_push($this->_collection, $element); } public function detach(Element $element) { $index = array_search($element, $this->_collection); if ($index !== FALSE) { unset($this->_collection[$index]); } return $index; } public function accept(Visitor $visitor) { foreach ($this->_collection as $element) { $element->accept($visitor); } } } // client $elementA = new ConcreteElementA("ElementA"); $elementB = new ConcreteElementB("ElementB"); $elementA2 = new ConcreteElementB("ElementA2"); $visitor1 = new ConcreteVisitor1(); $visitor2 = new ConcreteVisitor2(); $os = new ObjectStructure(); $os->attach($elementA); $os->attach($elementB); $os->attach($elementA2); $os->detach($elementA); $os->accept($visitor1); $os->accept($visitor2); ~~~ 例子: ``` /* 访问者模式的简单例子 我们都知道财务都是有账本的,这个账本就可以作为一个对象结构,而它其中的元素有两种,收入和支出,这满足我们访问者模式的要求,即元素的个数是稳定的,因为账本中的元素只能是收入和支出。 而查看账本的人可能有这样几种,比如老板,会计事务所的注会,财务主管,等等。而这些人在看账本的时候显然目的和行为是不同的。 首先我们给出单子的接口,它只有一个方法accept。 */ //单个单子的接口(相当于Element) interface Bill { public function accept(AccountBookViewer $viewer); } //其中的方法参数AccountBookViewer是一个账本访问者接口,接下来也就是实现类,收入单子和消费单子,或者说收入和支出类。 //消费的单子(支出类) class ConsumeBill implements Bill { private $amount = 0; //支出费用 private $item = ""; //支出项没有名称 public function __construct(float $amount, String $item) { $this->amount = $amount; $this->item = $item; } public function accept(AccountBookViewer $viewer) { //$viewer->view($this); $viewer->viewConsume($this); } public function getAmount() { return $this->amount; } public function getItem() { return $this->item; } } //收入单子(收入类) class IncomeBill implements Bill { private $amount = 0; private $item = ""; public function __construct(float $amount, String $item) { $this->amount = $amount; $this->item = $item; } public function accept(AccountBookViewer $viewer) { //$viewer->view($this); $viewer->viewIncome($this); } public function getAmount() { return $this->amount; } public function getItem() { return $this->item; } } //上面最关键的还是里面的accept方法,它直接让访问者访问自己,这相当于一次静态分派(文章最后进行解释),当然我们也可以不使用重载而直接给方法不同的名称。 //账单查看者接口(相当于Visitor) interface AccountBookViewer { //查看消费的单子 public function viewConsume(ConsumeBill $bill); //查看收入的单子 function viewIncome(IncomeBill $bill); } //注册会计师类,查看账本的类之一 class CPA implements AccountBookViewer { //注会在看账本时,如果是支出,则如果支出是工资,则需要看应该交的税交了没 public function viewConsume(ConsumeBill $bill) { if ($bill->getItem() === "工资") { print_r("注会查看工资是否交个人所得税。"); } } //如果是收入,则所有的收入都要交税 public function viewIncome(IncomeBill $bill) { print_r("注会查看收入交税了没。"); } } //老板类,查看账本的类之一 老板只关心收入和支出的总额,注册会计师只关注该交税的是否交税 class Boss implements AccountBookViewer { private $totalIncome = 0; private $totalConsume = 0; //老板只关注一共花了多少钱以及一共收入多少钱,其余并不关心 public function viewConsume(ConsumeBill $bill) { $this->totalConsume += $bill->getAmount(); } public function viewIncome(IncomeBill $bill) { $this->totalIncome += $bill->getAmount(); } public function getTotalIncome() { print_r("老板查看一共收入多少,数目是:" . $this->totalIncome); return $this->totalIncome; } public function getTotalConsume() { print_r("老板查看一共花费多少,数目是:" . $this->totalConsume); return $this->totalConsume; } } //账本类(相当于ObjectStruture对象结构) class AccountBook { //单子列表 private $billList = array(); //添加单子 public function addBill(Bill $bill) { $this->billList[] = $bill; } //供账本的查看者查看账本 public function show(AccountBookViewer $viewer) { foreach ($this->billList as $key => $bill) { $bill->accept($viewer); } } } //测试客户端 class Client { public static function main() { //账本类(相当于ObjectStruture对象结构) $accountBook = new AccountBook(); //添加两条收入 $accountBook->addBill(new IncomeBill(10000, "卖商品")); $accountBook->addBill(new IncomeBill(12000, "卖广告位")); //添加两条支出 $accountBook->addBill(new ConsumeBill(1000, "工资")); $accountBook->addBill(new ConsumeBill(2000, "材料费")); $boss = new Boss(); //账单查看者接口AccountBookViewer $cpa = new CPA(); //账单查看者接口AccountBookViewer //两个访问者分别访问账本 $accountBook->show($cpa); //会计查看账本 $accountBook->show($boss); //boss查看账本 $boss->getTotalConsume(); $boss->getTotalIncome(); } } /* 上面的代码中,可以这么理解,账本以及账本中的元素是非常稳定的,这些几乎不可能改变,而最容易改变的就是访问者这部分。 访问者模式最大的优点就是增加访问者非常容易,我们从代码上来看,如果要增加一个访问者,你只需要做一件事即可,那就是写一个类,实现AccountBookViewer接口,然后就可以直接调用AccountBook的show方法去访问账本了。 如果没使用访问者模式,一定会增加许多if else,而且每增加一个访问者,你都需要改你的if else,代码会显得非常臃肿,而且非常难以扩展和维护。 */ Client::main(); ```