行为型-访问者(Visitor)

4/8/2022 设计模式

摘要

JDK:1.8.0_202

# 一:场景问题

# 1.1 拓展客户管理功能

已有的功能:公司的客户分成两大类,一类是企业客户,一类是个人客户,现有的功能非常简单,就是能让客户提出服务申请。目前的程序结构如图所示:

classDiagram Customer <|.. EnterpriseCustomer Customer <|.. PersonalCustomer class Customer{ <<abstract>> -String customerId -String name +serviceRequest()* void } class EnterpriseCustomer{ -String linkman -String linkTelephone -String registerAddress +serviceRequest() void } class PersonalCustomer{ -String telephone -int age +serviceRequest() void }

现在的实现很简单,Customer的实现:

/**
 * 各种客户的父类
 */
public abstract class Customer {

    /**
     * 客户编号
     */
    private String customerId;

    /**
     * 客户名称
     */
    private String name;

    /**
     * 客户提出服务请求的方法,示意一下
     */
    public abstract void serviceRequest();

    public String getCustomerId() {
        return customerId;
    }

    public void setCustomerId(String customerId) {
        this.customerId = customerId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

企业客户的实现:

/**
 * 企业客户
 */
public class EnterpriseCustomer extends Customer {

    /**
     * 联系人
     */
    private String linkman;

    /**
     * 联系电话
     */
    private String linkTelephone;

    /**
     * 企业注册地址
     */
    private String registerAddress;

    /**
     * 企业客户提出服务请求的方法,示意一下
     */
    public void serviceRequest() {
        //企业客户提出的具体服务请求
        System.out.println(this.getName() + "企业提出服务请求");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

个人客户的实现:

/**
 * 个人客户
 */
public class PersonalCustomer extends Customer {

    /**
     * 联系电话
     */
    private String telephone;

    /**
     * 年龄
     */
    private int age;

    /**
     * 企业注册地址
     */
    private String registerAddress;

    /**
     * 个人客户提出服务请求的方法,示意一下
     */
    public void serviceRequest() {
        //个人客户提出的具体服务请求
        System.out.println("客户" + this.getName() + "提出服务请求");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

从上面的实现可以看出来,以前对客户的管理功能是很少的,现在随着业务的发展,需要加强对客户管理的功能,假设现在需要增加如下的功能:

客户对公司产品的偏好分析,针对企业客户和个人客户有不同的分析策略,主要是根据以往购买的历史、潜在购买意向等进行分析,对于企业客户还要添加上客户所在行业的发展趋势、客户的发展预期等的分析。

客户价值分析,针对企业客户和个人客户,有不同的分析方式和策略。主要是根据购买的金额大小、购买的产品和服务的多少、购买的频率等进行分析。

# 1.2 不用模式的解决方案

要实现上面要求的功能,也不是很困难,一个很基本的想法就是:既然不同类型的客户操作是不同的,那么在不同类型的客户里面分别实现这些功能,不就可以了。

按照上述的想法,这个时候的程序结构如图所示:

classDiagram Customer <|.. EnterpriseCustomer Customer <|.. PersionalCustomer class Customer{ <<abstract>> -String customerId -String name +serviceRequest(Visitor)* void +predilectionAnalyze(Visitor)* void +worthAnalyze(Visitor)* void } class EnterpriseCustomer{ -String linkman -String linkTelephone -String registerAddress +serviceRequest(Visitor) void +predilectionAnalyze(Visitor) void +worthAnalyze(Visitor) void } class PersionalCustomer{ -String telephone -Int age +serviceRequest(Visitor) void +prediectionAnalyze(Visitor) void +worthAnalyze(Visitor) void }

抽象的父类:

public abstract class Customer {

    private String customerId;

    private String name;

    public abstract void serviceRequest();

    /**
     * 客户对公司产品的偏好分析,示意一下
     */
    public abstract void predilectionAnalyze();

    /**
     * 客户价值分析,示意一下
     */
    public abstract void worthAnalyze();

    public String getCustomerId() {
        return customerId;
    }

    public void setCustomerId(String customerId) {
        this.customerId = customerId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

企业客户的示意实现:

public class EnterpriseCustomer extends Customer {

    private String linkman;

    private String linkTelephone;

    private String registerAddress;

    public void serviceRequest() {
        //企业客户提出的具体服务请求
        System.out.println(this.getName() + "企业提出服务请求");
    }

    /**
     * 企业客户对公司产品的偏好分析,示意一下
     */
    public void predilectionAnalyze() {
        //根据过往购买的历史、潜在购买意向
        //以及客户所在行业的发展趋势、客户的发展预期等的分析
        System.out.println("现在对企业客户" + this.getName() + "进行产品偏好分析");
    }

    /**
     * 企业客户价值分析,示意一下
     */
    public void worthAnalyze() {
        //根据购买的金额大小、购买的产品和服务的多少、购买的频率等进行分析
        //企业客户的标准会比个人客户的高
        System.out.println("现在对企业客户" + this.getName() + "进行价值分析");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

个人客户的示意实现:

public class EnterpriseCustomer extends Customer {

    private String linkman;

    private String linkTelephone;

    private String registerAddress;

    public void serviceRequest() {
        //企业客户提出的具体服务请求
        System.out.println(this.getName() + "企业提出服务请求");
    }

    /**
     * 企业客户对公司产品的偏好分析,示意一下
     */
    public void predilectionAnalyze() {
        //根据过往购买的历史、潜在购买意向
        //以及客户所在行业的发展趋势、客户的发展预期等的分析
        System.out.println("现在对企业客户" + this.getName() + "进行产品偏好分析");
    }

    /**
     * 企业客户价值分析,示意一下
     */
    public void worthAnalyze() {
        //根据购买的金额大小、购买的产品和服务的多少、购买的频率等进行分析
        //企业客户的标准会比个人客户的高
        System.out.println("现在对企业客户" + this.getName() + "进行价值分析");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

客户端:

public class Client {
    public static void main(String[] args) {
        //准备点测试数据
        Collection<Customer> colCustomer = preparedTestData();
        //循环对客户进行操作
        for (Customer cm : colCustomer) {
            //进行偏好分析
            cm.predilectionAnalyze();
            //进行价值分析
            cm.worthAnalyze();
        }
    }

    private static Collection<Customer> preparedTestData() {
        Collection<Customer> colCustomer = new ArrayList<>();
        //为了测试方便,准备点数据
        Customer cm1 = new EnterpriseCustomer();
        cm1.setName("ABC集团");
        colCustomer.add(cm1);

        Customer cm2 = new EnterpriseCustomer();
        cm2.setName("CDE公司");
        colCustomer.add(cm2);

        Customer cm3 = new PersonalCustomer();
        cm3.setName("张三");
        colCustomer.add(cm3);

        return colCustomer;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

运行结果:

# 1.3 有何问题

以很简单的方式,实现了要求的功能,这种实现有没有什么问题呢?仔细分析上面的实现,发现有两个主要的问题:

在企业客户和个人客户的类里面,都分别实现了提出服务请求、进行产品偏好分析、进行客户价值分析等功能,也就是说,这些功能的实现代码是混杂在同一个类里面的而且相同的功能分散到了不同的类中去实现,这会导致整个系统难以理解、难以维护

更为痛苦的是,采用这样的实现方式,如果要给客户扩展新的功能,比如前面提到的针对不同的客户进行需求调查;针对不同的客户进行满意度分析;客户消费预期分析等等。每次扩展,都需要改动企业客户的类和个人客户的类,当然也可以通过为它们扩展子类的方式,但是这样可能会造成过多的对象层次

那么有没有办法,能够在不改变客户这个对象结构中各元素类的前提下,为这些类定义新的功能?也就是要求不改变企业客户和个人客户类,就能为企业客户和个人客户类定义新的功能?

# 二:解决方案

用来解决上述问题的一个合理的解决方案,就是使用访问者模式。

访问者模式:表示一个作用与某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

# 2.1 解决思路

仔细分析上面的示例,对于客户这个对象结构,不想改变类,又要添加新的功能,很明显就需要一种动态的方式,在运行期间把功能动态地添加到对象结构中去。

有些朋友可能会想起装饰模式,**装饰模式可以实现为一个对象透明的添加功能,但装饰模式基本上是在现有的功能的基础之上进行功能添加,实际上是对现有功能的加强或者改造。**并不是在现有功能不改动的情况下,为对象添加新的功能。

看来需要另外寻找新的解决方式了,可以应用访问者模式来解决这个问题,访问者模式实现的基本思路如下:

首先定义一个接口来代表要新加入的功能,为了通用,也就是定义一个通用的功能方法来代表新加入的功能;

然后在对象结构上添加一个方法,作为通用的功能方法,也就是可以代表被添加的功能,在这个方法中传入具体的实现新功能的对象;

然后在对象结构的具体实现对象里面实现这个方法,回调传入具体的实现新功能的对象,就相当于调用到新功能上了;

接下来的步骤就是提供实现新功能的对象

最后再提供一个能够循环访问整个对象结构的类,让这个类来提供符合客户端业务需求的方法,来满足客户端调用的需要;

这样一来,只要提供实现新功能的对象给对象结构,就可以为这些对象添加新的功能,由于在对象结构中定义的方法是通用的功能方法,所以什么新功能都可以加入。

# 2.2 模式结构和说明

classDiagram Visitor <|.. ConcreteVisitor1 Visitor <|.. ConcreteVisitor2 Element <|.. ConcreteElementA Element <|.. ConcreteElementB Element <--o ObjectStructure Visitor <.. Client ObjectStructure <.. Client class Visitor{ <<interface>> +visitConcreteElementA(ConcreteElementA)* void +visitConcreteElementB(ConcreteElementB)* void } class ConcreteVisitor1{ +visitConcreteElementA(ConcreteElementA) void +visitConcreteElementB(ConcreteElementB) void } class ConcreteVisitor2{ +visitConcreteElementA(ConcreteElementA) void +visitConcreteElementB(ConcreteElementB) void } class Element{ <<interface>> +accept(Visitor)* void } class ConcreteElementA{ +accept(Visitor) void +opertionA() void } class ConcreteElementB{ +accept(Visitor) void +opertionB() void }
  • Visitor:访问者接口,为所有的访问者对象声明一个visit方法,用来代表为对象结构添加的功能,理论上可以代表任意的功能
  • ConcreteVisitor:具体的访问者实现对象,实现要真正被添加到对象结构中的功能。
  • Element:抽象的元素对象,对象结构的顶层接口,定义接受访问的操作。
  • ConcreteElement:具体元素对象,对象结构中具体的对象,也是被访问的对象,通常会回调访问者的真实功能,同时开放自身的数据供访问者使用。
  • ObjectStructure:对象结构,通常包含多个被访问的对象,它可以遍历这多个被访问的对象,也可以让访问者访问它的元素。可以是一个复合或是一个集合,如一个列表或无序集合。

但是请注意:这个ObjectStructure并不是我们在前面讲到的对象结构,前面一直讲的对象结构是指的一系列对象的定义结构,是概念上的东西而ObjectStructure可以看成是对象结构中的一系列对象的一个集合,是用来辅助客户端访问这一系列对象的,所以为了不造成困惑,后面提到 ObjectStructure 的时候,就用英文名称来代替,不把它翻译成中文。

# 2.3 示例代码

首先需要定义一个接口来代表要新加入的功能,把它称作访问者,访问谁呢?当然是访问对象结构中的对象了。既然是访问,不能空手而去吧,这些访问者在进行访问的时候,就会携带新的功能,也就是说,访问者携带着需要添加的新的功能去访问对象结构中的对象,就相当于给对象结构中的对象添加了新的功能。示例代码如下:

/**
 * 访问者接口
 */
public interface Visitor {

    /**
     * 访问元素A,相当于给元素A添加访问者的功能
     *
     * @param elementA 元素A的对象
     */
    void visitConcreteElementA(ConcreteElementA elementA);

    /**
     * 访问元素B,相当于给元素B添加访问者的功能
     *
     * @param elementB 元素B的对象
     */
    void visitConcreteElementB(ConcreteElementB elementB);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

抽象的元素对象定义:

/**
 * 被访问的元素的接口
 */
public abstract class Element {

    /**
     * 接受访问者的访问
     *
     * @param visitor 访问者对象
     */
    public abstract void accept(Visitor visitor);
}
1
2
3
4
5
6
7
8
9
10
11
12

元素对象具体实现:(ConcreteElementA.java 和 ConcreteElementB.java)

/**
 * 具体元素的实现对象
 */
public class ConcreteElementA extends Element {

    @Override
    public void accept(Visitor visitor) {
        // 回调访问者对象的相应方法
        visitor.visitConcreteElementA(this);
    }

    /**
     * 示例方法,表示元素已有的功能实现
     */
    public void opertionA() {
        // 已有的功能实现
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * 具体元素的实现对象
 */
public class ConcreteElementB extends Element {

    @Override
    public void accept(Visitor visitor) {
        // 回调访问者对象的相应方法
        visitor.visitConcreteElementB(this);
    }

    /**
     * 示例方法,表示元素已有的功能实现
     */
    public void opertionB() {
        // 已有的功能实现
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

访问者:

/**
 * 具体的访问者实现
 */
public class ConcreteVisitor1 implements Visitor {

    @Override
    public void visitConcreteElementA(ConcreteElementA element) {
        //把去访问ConcreteElementA时,需要执行的功能实现在这里
        //可能需要访问元素已有的功能,比如:
        element.opertionA();
    }

    @Override
    public void visitConcreteElementB(ConcreteElementB element) {
        //把去访问ConcreteElementB时,需要执行的功能实现在这里
        //可能需要访问元素已有的功能,比如:
        element.opertionB();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

ObjectStructure的实现:

/**
 * 对象结构,通常在这里对元素对象进行遍历,让访问者能访问到所有的元素
 */
public class ObjectStructure {

    /**
     * 示意,表示对象结构,可以是一个组合结构或是集合
     */
    private Collection<Element> col = new ArrayList<Element>();

    /**
     * 示意方法,提供给客户端操作的高层接口
     *
     * @param visitor 客户端需要使用的访问者
     */
    public void handleRequest(Visitor visitor) {
        //循环对象结构中的元素,接受访问
        for (Element ele : col) {
            ele.accept(visitor);
        }
    }

    /**
     * 示意方法,组建对象结构,向对象结构中添加元素。
     * 不同的对象结构有不同的构建方式
     *
     * @param ele 加入到对象结构的元素
     */
    public void addElement(Element ele) {
        this.col.add(ele);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

客户端:

public class Client {

    public static void main(String[] args) {
        //创建ObjectStructure
        ObjectStructure os = new ObjectStructure();
        //创建要加入对象结构的元素
        Element eleA = new ConcreteElementA();
        Element eleB = new ConcreteElementB();
        //把元素加入对象结构
        os.addElement(eleA);
        os.addElement(eleB);
        //创建访问者
        Visitor visitor = new ConcreteVisitor1();
        //调用业务处理的方法
        os.handleRequest(visitor);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 2.4 重写案例

要使用访问者模式来重写示例,首先就要按照访问者模式的结构,分离出两个类层次来,一个是对应于元素的类层次,一个是对应于访问者的类层次

对于对应于元素的类层次,现在已经有了,就是客户的对象层次而对应于访问者的类层次,现在还没有,不过,按照访问者模式的结构,应该是先定义一个访问者接口,然后把每种业务实现成为一个单独的访问者对象,也就是说应该使用一个访问者对象来实现对客户的偏好分析,而用另外一个访问者对象来实现对客户的价值分析。

在分离好两个类层次过后,为了方便客户端的访问,定义一个ObjectStructure,其实就类似于前面示例中的客户管理的业务对象。新的示例的结构如图所示:

classDiagram Visitor <|.. ServiceRequestVisitor Visitor <|.. PredilectionAnalyzeVisitor Customer <|.. EnterpriseCustomer Customer <|.. PersionalCustomer Customer "0..*" <--o "1" ObjectStructure Visitor <.. Client ObjectStructure <.. Client class Visitor{ <<interface>> +visitEnterpriseCustomer(EnterpriseCustomer)* void +visitPersionalCustomer(PersionalCustomer)* void } class ServiceRequestVisitor{ +visitEnterpriseCustomer(EnterpriseCustomer) void +visitPersionalCustomer(PersionalCustomer) void } class PredilectionAnalyzeVisitor{ +visitEnterpriseCustomer(EnterpriseCustomer) void +visitPersionalCustomer(PersionalCustomer) void } class Customer{ <<abstract>> -String customerId -String name +accept(Visitor)* void } class EnterpriseCustomer{ -String linkman -String linkTelephone -String registerAddress +accept(Visitor) void } class ObjectStructure{ -Collection~Customer~ col +handleRequest(Visitor) void +addElement(Customer) void } class PersionalCustomer{ -String telephone -Int age +accept(Visitor) void }

仔细查看图所示的程序结构示意图,会发现,在图上没有出现对客户进行价值分析的功能了。这是为了示范"使用访问者模式来实现示例功能过后,可以很容易的给对象结构增加新的功能",所以先不做这个功能,等都实现好了,再来扩展这个功能

先来看看Customer的代码,Customer相当于访问者模式中的Element,它的实现跟以前相比有如下的改变:

新增一个接受访问者访问的方法;

把能够分离出去放到访问者中实现的方法,从Customer中删除掉,包括:客户提出服务请求的方法、对客户进行偏好分析的方法、对客户进行价值分析的方法等;

public abstract class Customer {

    private String customerId;

    private String name;

    /**
     * 接受访问者的访问
     *
     * @param visitor 访问者对象
     */
    public abstract void accept(Visitor visitor);

    public String getCustomerId() {
        return customerId;
    }

    public void setCustomerId(String customerId) {
        this.customerId = customerId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

企业客户:

public class EnterpriseCustomer extends Customer {

    private String linkman;

    private String linkTelephone;

    private String registerAddress;

    public void accept(Visitor visitor) {
        //回调访问者对象的相应方法
        visitor.visitEnterpriseCustomer(this);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

个人客户:

public class PersonalCustomer extends Customer {

    private String telephone;

    private int age;

    public void accept(Visitor visitor) {
        //回调访问者对象的相应方法
        visitor.visitPersonalCustomer(this);
    }
}
1
2
3
4
5
6
7
8
9
10

访问者接口定义:

/**
 * 访问者接口
 */
public interface Visitor {
    /**
     * 访问企业客户,相当于给企业客户添加访问者的功能
     * @param ec 企业客户的对象
     */
    public void visitEnterpriseCustomer(EnterpriseCustomer ec);
    /**
     * 访问个人客户,相当于给个人客户添加访问者的功能
     * @param pc 个人客户的对象
     */
    public void visitPersonalCustomer(PersonalCustomer pc);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

实现客户提出服务请求的功能的访问者:

/**
 * 具体的访问者,实现客户提出服务请求的功能
 */
public class ServiceRequestVisitor implements Visitor {

    public void visitEnterpriseCustomer(EnterpriseCustomer ec) {
        //企业客户提出的具体服务请求
        System.out.println(ec.getName() + "企业提出服务请求");
    }

    public void visitPersonalCustomer(PersonalCustomer pc) {
        //个人客户提出的具体服务请求
        System.out.println("客户" + pc.getName() + "提出服务请求");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

实现对客户偏好分析功能的访问者:

/**
 * 具体的访问者,实现对客户的偏好分析
 */
public class PredilectionAnalyzeVisitor implements Visitor {

    public void visitEnterpriseCustomer(EnterpriseCustomer ec) {
        //根据过往购买的历史、潜在购买意向
        //以及客户所在行业的发展趋势、客户的发展预期等的分析
        System.out.println("现在对企业客户" + ec.getName() + "进行产品偏好分析");
    }

    public void visitPersonalCustomer(PersonalCustomer pc) {
        System.out.println("现在对个人客户" + pc.getName() + "进行产品偏好分析");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

ObjectStructure的实现:

public class ObjectStructure {

    /**
     * 要操作的客户集合
     */
    private Collection<Customer> col = new ArrayList<>();

    /**
     * 提供给客户端操作的高层接口,具体的功能由客户端传入的访问者决定
     *
     * @param visitor 客户端需要使用的访问者
     */
    public void handleRequest(Visitor visitor) {
        //循环对象结构中的元素,接受访问
        for (Customer cm : col) {
            cm.accept(visitor);
        }
    }

    /**
     * 组建对象结构,向对象结构中添加元素。
     * 不同的对象结构有不同的构建方式
     *
     * @param ele 加入到对象结构的元素
     */
    public void addElement(Customer ele) {
        this.col.add(ele);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

客户端:

public class Client1 {

    public static void main(String[] args) {
        // 创建ObjectStructure
        ObjectStructure os = new ObjectStructure();
        // 准备点测试数据,创建客户对象,并加入ObjectStructure
        Customer cm1 = new EnterpriseCustomer();
        cm1.setName("ABC集团");
        os.addElement(cm1);

        Customer cm2 = new EnterpriseCustomer();
        cm2.setName("CDE公司");
        os.addElement(cm2);

        Customer cm3 = new PersonalCustomer();
        cm3.setName("张三");
        os.addElement(cm3);

        //客户提出服务请求,传入服务请求的Visitor
        ServiceRequestVisitor srVisitor = new ServiceRequestVisitor();
        os.handleRequest(srVisitor);

        // 要对客户进行偏好分析,传入偏好分析的Visitor
        PredilectionAnalyzeVisitor paVisitor = new PredilectionAnalyzeVisitor();
        os.handleRequest(paVisitor);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

运行结果:

使用访问者模式来重新实现了前面示例的功能,把各类相同的功能放到单独的访问者对象里面,使得代码不再杂乱,系统结构也更清晰,能方便的维护了,算是解决了前面示例的一个问题

前面在示例的时候,故意留下了一个对客户进行价值分析的功能没有实现,那么接下来就看看如何把这个功能增加到已有的系统中。在访问者模式中要给对象结构增加新的功能,只需要把新的功能实现成为访问者,然后在客户端调用的时候使用这个访问者对象来访问对象结构即可。

实现对客户价值分析功能的访问者

/**
 * 具体的访问者,实现对客户价值分析
 */
public class WorthAnalyzeVisitor implements Visitor {

    public void visitEnterpriseCustomer(EnterpriseCustomer ec) {
        //根据购买的金额大小、购买的产品和服务的多少、购买的频率等进行分析
        //企业客户的标准会比个人客户的高
        System.out.println("现在对企业客户" + ec.getName() + "进行价值分析");
    }

    public void visitPersonalCustomer(PersonalCustomer pc) {
        System.out.println("现在对个人客户" + pc.getName() + "进行价值分析");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

客户端:

public class Client2 {

    public static void main(String[] args) {
        // 创建ObjectStructure
        ObjectStructure os = new ObjectStructure();
        // 准备点测试数据,创建客户对象,并加入ObjectStructure
        Customer cm1 = new EnterpriseCustomer();
        cm1.setName("ABC集团");
        os.addElement(cm1);

        Customer cm2 = new EnterpriseCustomer();
        cm2.setName("CDE公司");
        os.addElement(cm2);

        Customer cm3 = new PersonalCustomer();
        cm3.setName("张三");
        os.addElement(cm3);

        //客户提出服务请求,传入服务请求的Visitor
        ServiceRequestVisitor srVisitor = new ServiceRequestVisitor();
        os.handleRequest(srVisitor);

        // 要对客户进行偏好分析,传入偏好分析的Visitor
        PredilectionAnalyzeVisitor paVisitor = new PredilectionAnalyzeVisitor();
        os.handleRequest(paVisitor);

        // 要对客户进行价值分析,传入价值分析的Visitor
        WorthAnalyzeVisitor waVisitor = new WorthAnalyzeVisitor();
        os.handleRequest(waVisitor);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 三:模式讲解

# 3.1 访问者模式优缺点

1. 好的扩展性

能够在不修改对象结构中的元素的情况下,给对象结构中的元素添加新的功能。

2. 好的复用性

可以通过访问者来定义整个对象结构通用的功能,从而提高复用程度。

3. 分离无关行为

可以通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

4. 对象结构变化很困难

不适用于对象结构中的类经常变化的情况,因为对象结构发生了改变,访问者的接口和访问者的实现都要发生相应的改变,代价太高。

5. 破坏封装

访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructrue,这破坏了对象的封装性。

# 3.2 思考访问者模式

1. 访问者模式的本质

访问者模式的本质:预留通路,回调实现。

仔细思考访问者模式,它的实现主要就是通过预先定义好调用的通路,在被访问的对象上定义accept方法,在访问者的对象上定义visit方法;然后在调用真正发生的时候,通过两次分发的技术,利用预先定义好的通路,回调到访问者具体的实现上

明白了访问者模式的本质,就可以在定义一些通用功能,或者设计工具类的时候让访问者模式派上大用场了。你可以把已经实现好的一些功能,把它们作为已有的对象结构,因为在今后可能会根据实际需要给它们增加新的功能,甚至你希望开放接口来让其它开发人员扩展这些功能,那么你就可以用访问者模式来设计,在这个对象结构上预留好通用的调用通路,在以后添加功能,或者是其它开发人员来扩展的时候,只需要提供新的访问者实现,就能够很好的加入到系统中来了。

2. 何时选用访问者模式

建议在如下情况中,选用访问者模式:

如果想对一个对象结构,实施一些依赖于对象结构中的具体类的操作,可以使用访问者模式。

如果想对一个对象结构中的各个元素,进行很多不同的而且不相关的操作,为了避免这些操作使得类变得杂乱,可以使用访问者模式,把这些操作分散到不同的访问者对象中去,每个访问者对象实现同一类功能。

如果对象结构很少变动,但是需要经常给对象结构中的元素对象定义新的操作,可以使用访问者模式。

# 3.3 相关模式

1. 访问者模式和组合模式

这两个模式可以组合使用。

如同前面示例的那样,通过访问者模式给组合对象预留下扩展功能的接口,使得给组合模式的对象结构添加功能非常容易。

2. 访问者模式和装饰模式

这两个模式从表面看功能有些相似,都能够实现在不修改原对象结构的情况下修改原对象的功能。但是装饰模式更多的是实现对已有功能加强、或者修改、或者完全全新实现;而访问者模式更多的是实现给对象结构添加新的功能

3. 访问者模式和解释器模式

这两个模式可以组合使用。

解释器模式在构建抽象语法树的时候,是使用组合模式来构建的,也就是说解释器模式解释并执行的抽象语法树是一个组合对象结构,这个组合对象结构是很少变动的,但是可能经常需要为解释器增加新的功能,实现对同一对象结构的不同解释和执行的功能,这正好是访问者模式的优势所在,因此这在使用解释器模式的时候通常会组合访问者模式来使用。

# 四:JDK

# 五:参考文献

最后更新: 4/9/2022, 7:57:03 PM