目 录CONTENT

文章目录

访问者模式

半糖
2024-09-12 / 0 评论 / 0 点赞 / 2 阅读 / 14213 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2024-09-12,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

名称

访问者模式(VISITOR)

目的

表示一个作用于某对象结构(ObjectStructure)中的各元素(Element)的操作(accept),在不改变各元素的类(Element类)的前提下定义作用于这些元素的新操作。

适用性

在下列情况下使用Visitor模式:

  • 一个对象结构包含很多类对象,它们有不同的接口,想对这些对象实施一些依赖于其具体类的操作。

  • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,想避免让这些操作“污染”这些对象的类。Visitor可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。

  • 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。

结构

访问者(Visitor):为该对象结构中ConcreteElement的每一个类声明一个visit操作。该操作的名字和特征标识了发送Visit请求给该访问者的那个类,这使得访问者可以确定正被访问元素的具体的类,这样访问者就可以通过该元素的特定接口直接访问它。

具体访问者(ConcreteVisitor):实现每个由Visitor声明的操作。每个操作实现本算法的一部分,而该算法片断就是对应于结构中对象的类。ConcreteVisitor为该算法提供了上下文并存储它的局部状态,这一状态常常在遍历该结构的过程中累积结果。

元素(Element):定义一个accept操作,它以一个访问者为参数。

具体元素(ConcreteElement):实现Accept操作,该操作以一个访问者为参数。

对象结构(ObjectStructure)

  • 能枚举它的元素。

  • 可以提供一个高层的接口以允许该访问者访问它的元素。

  • 可以是一个复合(参见Composite(4.3))或是一个集合,如一个列表或一个无序集合。

协作

  1. 一个使用Visitor模式的客户必须创建一个ConcreteVisitor对象,然后遍历该对象结构,并用该访问者访问每一个元素。

  2. 当一个元素被访问时,它调用对应于它的类的Visitor操作。如果必要,该元素将自身作为这个操作的一个参数以便该访问者访问它的状态。

效果

优点

  1. 访问者模式使得易于增加新的操作。访问者使得增加依赖于复杂对象结构的构件的操作变得容易了,仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作。相反,如果每个功能都分散在多个类之上的话,定义新的操作时必须修改每一类。

  2. 访问者集中相关的操作而分离无关的操作。相关的行为不是分布在定义该对象结构的各个类上,而是集中在一个访问者中,无关行为被分别放在它们各自的访问者子类中。这就既简化了这些元素的类,也简化了在这些具体访问者中定义的算法,所有与它的算法相关的数据结构都可以被隐藏在各个具体访问者中。

  3. 符合单一职责原则。每个类只负责处理自己的对象,而具体的处理逻辑则交由访问者类完成。

缺点

  1. 增加新的ConcreteElement类很困难。Visitor模式使得难以增加新的Element的子类,每添加一个新的ConcreteElement都要在Vistor中添加一个新的抽象操作,并在每一个ConcretVisitor类中实现相应的操作。有时可以在Visitor中提供一个缺省的实现,这一实现可以被大多数的ConcreteVisitor继承,可以减少在ConcreteVisitor定义新ConcreteElement带来的操作。
    在应用访问者模式时需要考虑关键的问题是系统的哪个部分会经常变化,是作用于对象结构上的算法呢还是构成该结构的各个对象的类(Element)。如果老是有新的ConcretElement类加入进来的话,Vistor类层次将变得难以维护,在这种情况下,直接在构成该对象结构的类中定义这些操作可能更容易一些;如果Element类层次是稳定的,需要不断地增加操作或修改算法,访问者模式就能起到很好的帮助。

  2. 增加系统复杂度。需要为每一个Element类都定义一个访问者接口,增加了系统的复杂性和理解难度。

实现

  1. 每一个对象结构将有一个相关的Visitor类,这个抽象的访问者类为定义对象结构的每一个ConcreteElement类声明一个VisitConcreteElement操作。每一个Visitor上的Visit操作声明它的参数为一个特定的ConcreteElement,以允许该Visitor直接访问ConcreteElement的接口。ConcreteVistor类重定义每一个Visit操作,从而为相应的ConcreteElement类实现与特定访问者相关的行为。

  2. 每个ConcreteElement类实现一个Accept操作,这个操作调用访问者中相应于本ConcreteElement类的Visit...的操作,这样最终得到调用的操作不仅依赖于该元素的类也依赖于访问者的类。

下面是当应用Visitor模式时产生的其他两个实现问题:

(1)单分派和双分派。有些语言(如CLOS)本身就支持双分派,而有些语言(如Java、Smalltalk、C++)只支持单分派,双分派需要使用访问者模式实现。

(2)谁负责遍历对象结构。一个访问者必须访问这个对象结构的每一个元素,问题是,它怎样做?我们可以将遍历的责任放到下面三个地方中的任意一个:对象结构中,访问者中,或一个独立的迭代器对象中(参见迭代器模式)。通常由对象结构负责迭代。一个集合只需对它的元素进行迭代,并对每一个元素调用Accept操作。而一个复合通常让Accept操作遍历该元素的各子构件并对它们中的每一个递归地调用Accept。另一个解决方案是使用一个迭代器来访问各个元素。既可以使用内部迭代器也可以使用外部迭代器,到底用哪一个取决于哪一个可用和哪一个最有效。甚至可以将遍历算法放在访问者中,尽管这样将导致对每一个聚合ConcreteElement,在每一个ConcreteVisitor中都要复制遍历的代码,将该遍历策略放在访问者中的主要原因是想实现一个特别复杂的遍历,它依赖于对该对象结构的操作结果。

应用

问题

在医院里,划价员和药方工作者会根据医生开的药方上的药品执行不同的操作,一个负责划价,一个负责拿药,不同的药品和不同的工作者共同组成一个具体的操作,例如:“划价员甲给药品划价”,具体操作是由划价员和药品组成,如果是药品B,就变成了“划价员甲给药品B划价”,这就是双分派模式,也就是访问者模式。在下列示例中,将以工作人员作为访问者,药品作为元素,讲解访问者。

示例

UML

代码示例

访问者:工作人员(类名用的VIsitor,不用在意这些细节)

package com.ysj.part5.visitor.visitor;

import com.ysj.part5.visitor.concreteElement.MedicineA;
import com.ysj.part5.visitor.concreteElement.MedicineB;
import com.ysj.part5.visitor.concreteElement.MedicineC;

/**
 * 抽象访问者
 */
public abstract class Visitor {
    protected String name;

    public void setName(String name) {
        this.name = name;
    }


    public abstract void visitor(MedicineA a);

    public abstract void visitor(MedicineB b);

    /**
     * 增加新的ConcreteElement类很困难Visitor模式使得难以增加新的Element的子类。每
     * 添加一个新的ConcreteElement都要在Vistor中添加一个新的抽象操作,并在每一个
     * ConcretVisitor类中实现相应的操作
     * 有时可以在 Visitor中提供一个缺省的实现,这一实现可以被大多数的ConcreteVisitor继承,
     *
     * @param medicineC
     */
    public void visitor(MedicineC medicineC) {
        System.out.println("新增元素后提供的一个缺省的实现,不一定有用");
    }


}

具体访问者:划价员、药房工作者

package com.ysj.part5.visitor.concreteVisitor;

import com.ysj.part5.visitor.concreteElement.MedicineA;
import com.ysj.part5.visitor.concreteElement.MedicineB;
import com.ysj.part5.visitor.element.Medicine;
import com.ysj.part5.visitor.visitor.Visitor;

/**
 * 划价员(具体访问者)
 */
public class Charger extends Visitor {

    public void visitor(MedicineA a) {
        System.out.println("划价员:" + name +"给药" + a.getName() +"划价:" + a.getPrice());
    }

    public void visitor(MedicineB b) {
        System.out.println("划价员:" + name +"给药" + b.getName() +"划价:" + b.getPrice());
    }

}
package com.ysj.part5.visitor.concreteVisitor;

import com.ysj.part5.visitor.concreteElement.MedicineA;
import com.ysj.part5.visitor.concreteElement.MedicineB;
import com.ysj.part5.visitor.concreteElement.MedicineC;
import com.ysj.part5.visitor.element.Medicine;
import com.ysj.part5.visitor.visitor.Visitor;

/**
 * 药房工作者(具体访问者)
 */
public class WorkerOfPharmacy extends Visitor {

    public void visitor(MedicineA a) {
        System.out.println("药房工作者:" + name + "拿药 :" + a.getName());
    }

    public void visitor(MedicineB b) {
        System.out.println("药房工作者:" + name + "拿药 :" + b.getName());
    }

}

元素:药品

package com.ysj.part5.visitor.element;

import com.ysj.part5.visitor.visitor.Visitor;

/**
 * 药(抽象元素)
 */
public abstract class Medicine {
    protected String name;
    protected double price;

    public Medicine (String name,double price){
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public abstract void accept(Visitor visitor);
}

具体药品:药品A、药品B、药品C

package com.ysj.part5.visitor.concreteElement;

import com.ysj.part5.visitor.element.Medicine;
import com.ysj.part5.visitor.visitor.Visitor;

/**
 * 药物A(具体元素)
 */
public class MedicineA extends Medicine {

    public MedicineA(String name, double price) {
        super(name, price);
    }

    public void accept(Visitor visitor) {
        visitor.visitor(this);
    }
}
package com.ysj.part5.visitor.concreteElement;

import com.ysj.part5.visitor.element.Medicine;
import com.ysj.part5.visitor.visitor.Visitor;


/**
 * 药物B(具体元素)
 */
public class MedicineB extends Medicine {

    public MedicineB(String name, double price) {
        super(name, price);
    }

    public void accept(Visitor visitor) {
        visitor.visitor(this);
    }
}
package com.ysj.part5.visitor.concreteElement;

import com.ysj.part5.visitor.element.Medicine;
import com.ysj.part5.visitor.visitor.Visitor;


/**
 * 药物B(具体元素)
 */
public class MedicineC extends Medicine {

    public MedicineC(String name, double price) {
        super(name, price);
    }

    public void accept(Visitor visitor) {
        visitor.visitor(this);
    }
}

对象结构:药方

package com.ysj.part5.visitor.objectStructure;

import com.ysj.part5.visitor.element.Medicine;
import com.ysj.part5.visitor.visitor.Visitor;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 药方(对象结构)
 */
public class Presciption {
    List<Medicine> list = new ArrayList<Medicine>();

    public void accept(Visitor visitor){
        Iterator<Medicine> iterator = list.iterator();
        while (iterator.hasNext()) {
            iterator.next().accept(visitor);
        }
    }

    public void addMedicine(Medicine medicine){
        list.add(medicine);
    }

    public void removeMedicien(Medicine medicine){
        list.remove(medicine);
    }
}

客户端:调用方

package com.ysj.part5.visitor;

import com.ysj.part5.visitor.concreteElement.MedicineA;
import com.ysj.part5.visitor.concreteElement.MedicineB;
import com.ysj.part5.visitor.concreteElement.MedicineC;
import com.ysj.part5.visitor.concreteVisitor.Charger;
import com.ysj.part5.visitor.concreteVisitor.WorkerOfPharmacy;
import com.ysj.part5.visitor.element.Medicine;
import com.ysj.part5.visitor.objectStructure.Presciption;
import com.ysj.part5.visitor.visitor.Visitor;

/**
 * 访问者模式
 * 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作
 * 示意图:/doc/diagram/访问者模式示意图.jpg
 */
public class Client {
    public static void main(String[] args) {
        // 元素:药物
        Medicine a = new MedicineA("板蓝根", 11.0);
        Medicine b = new MedicineB("感康", 14.3);
        Medicine c = new MedicineC("连花清瘟颗粒", 998);

        // 对象结构:处方
        Presciption presciption = new Presciption();
        presciption.addMedicine(a);
        presciption.addMedicine(b);
        presciption.addMedicine(c);

        // 访问者:划价员、药房工作者
        Visitor charger = new Charger();
        charger.setName("张三");
        Visitor workerOfPharmacy = new WorkerOfPharmacy();
        workerOfPharmacy.setName("李四");

        // 调用不同访问者去执行同一药方
        presciption.accept(charger);
        System.out.println("----------------可以从上下两处看到同一结构对象对于不同访问者有不同的操作实现,而定义新操作,也就是定义新的具体访问者---------------------");
        presciption.accept(workerOfPharmacy);
    }

}

已知应用

Spring Framework

Spring 框架中的 ApplicationEvent 和 ApplicationListener 接口也体现了访问者模式。Spring 容器可以用来管理事件的发布和监听。

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区