《Android源码设计模式》之访问者模式

访问者模式介绍

访问者模式是一种将数据库操作与数据结构分离的设计模式,它是所有设计模式中最复杂的一个,但是使用频率并不高。访问者模式的基本想法是,软件系统中有用一个由许多对象构成的、比较稳定的对象结构,这些对象的类都有一个accept方法用来接受访问者对象的访问。访问者是一个接口,它拥有一个visit方法,这个方法对访问到的对象结构中不同类型的元素作出不同的处理。在对象结构的一次访问过程中,我没遍历整个对象结构,对每一个元素都实施accept方法,在每一个元素的accept方法中会调用访问者的visit方法,从而使访问者得以处理对象结构的每一个元素,我没可以针对对象结构设计不同的访问者类来完成不同的操作,达到区别对待的效果。

访问者模式的定义

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。

访问者模式的使用场景

(1) 对象结构比较稳定,但经常需要在此对象结构上定义新的操作。
  (2) 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。

访问者模式的UML类图

在这里插入图片描述
角色介绍:

  • Visitor:接口或者抽象类,它定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上讲与元素是一样的,因此,访问者模式要求元素的嫘祖要稳定,如果经常添加、移除元素类,必然会导致频繁地修改Visitor接口,如果出现这种情况,则说明不适合使用访问者模式。
  • ConcreteVisitor:具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。
  • Element:元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。
  • ElementA、ElementB:具体的元素类,它提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
  • ObjectStructure:定义当中所提到的对象结构,对象结构是一个抽象表达,它内部管理了元素集合,并且可以迭代这些元素访问者访问。
访问者模式的简单示例

在年终时,公司给员工进行业绩考核,这些评定都是由公司高层来负责,不同领域的管理人员对员工的评定标准是不一样的,为了简单的说明问题,我没把员工简化为工程师和经理,评定员工的分别为CEO和CTO,我们假定CTO只关注工程师的代码量、经理的新产品数量,而CEO关注的是工程师的KPI和经理的KPI以及新产品数量,从中可以看出,CEO和CTO对不同员工的关注点是不一样的,这就需要对员工类型进行不同的处理。访问者模式就可以派上用场了,下面看代码相关的实现:

package com.guifa.patterndemo.visitorpattern;

import java.util.Random;

// 员工基类
public abstract class Staff {

    public String name;
    public int kpi;

    public Staff(String name) {
        this.name = name;
        this.kpi = new Random().nextInt(10);
    }

    // 接受Visitor的访问
    public abstract void accept(Visitor visitor);
}

Staff类定义了员工的基本信息以及一个accept方法,accept方法表示接受访问者的访问,由子类具体实现。下面看看工程师和经理的相关代码:

package com.guifa.patterndemo.visitorpattern;

import java.util.Random;

// 工程师
public class Engineer extends Staff {

    public Engineer(String name) {
        super(name);
    }

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

    // 工程师这一年写的代码数量
    public int getCodeLines() {
        return new Random().nextInt(10 * 1000);
    }
}
package com.guifa.patterndemo.visitorpattern;

import java.util.Random;

// 经理类型
public class Manager extends Staff {

    // 产品数量
    private int products;

    public Manager(String name) {
        super(name);
        products = new Random().nextInt(10);
    }

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

    public int getProducts() {
        return products;
    }
}

在工程师类中添加了获取代码行数的函数,而在经理类型中则添加了获取新产品数量的函数,他们的职责是不一样的,也正是由于它们的差异性才使得访问者模式能够发挥它的作用。Staff、Engineer、Manager3个类型就是对象结构,这些类型相对稳定,不会发生变化。
然后将这些员工添加到一个业务报表类中,公司高层可以通过该报表类的showReport函数查看所有员工的业绩,代码如下:

package com.guifa.patterndemo.visitorpattern;

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

// 员工报表类
public class BusinessReport {

    List<Staff> mStaffs = new ArrayList<>();

    public BusinessReport() {
        mStaffs.add(new Manager("王经理"));
        mStaffs.add(new Engineer("工程师-A"));
        mStaffs.add(new Engineer("工程师-B"));
        mStaffs.add(new Engineer("工程师-C"));
        mStaffs.add(new Engineer("工程师-D"));
    }

    /**
     * 为访问者展示报表
     *
     * @param visitor Visitor公司高层,如CEO、CTO
     */
    public void showReport(Visitor visitor) {
        for (Staff staff : mStaffs) {
            staff.accept(visitor);
        }
    }
}

下面看看Visitor类型的定义,Visitor声明了两个visit函数,分别是对工程师和经理的访问函数,具体代码如下:

package com.guifa.patterndemo.visitorpattern;

public interface Visitor {
    
    // 访问工程师类型
    public void visit(Engineer engineer);

    // 访问经理类型
    public void visit(Manager manager);
}

首先定义了一个Visitor接口,该接口有两个visit函数,参数分别为Engineer、Manager,也就是说对于Engineer、Manager的访问会调用两个不同的方法,以此达成区别对待、差异化处理。具体的实现类为CEOVisitor、CTOVisitor类,具体代码如下:

package com.guifa.patterndemo.visitorpattern;

// CEO访问者,只关注业绩
public class CEOVisitor implements Visitor {
    @Override
    public void visit(Engineer engineer) {
        System.out.println("工程师:" + engineer.name + ",KPI:" + engineer.kpi);
    }

    @Override
    public void visit(Manager manager) {
        System.out.println("经理:" + manager.name + ",KPI:" + manager.kpi + ",新产品数量:" + manager.getProducts());
    }
}
package com.guifa.patterndemo.visitorpattern;

public class CTOVisitor implements Visitor {
    @Override
    public void visit(Engineer engineer) {
        System.out.println("工程师:" + engineer.name + ",代码行数:" + engineer.getCodeLines());
    }

    @Override
    public void visit(Manager manager) {
        System.out.println("经理:" + manager.name + ",新产品数量:" + manager.getProducts());
    }
}

在CEO的访问者中,CEO只关注Engineer员工的KPI,而对于Manager类型的员工除了KPI之外还有Manager本年度新开发产品的数量。两类员工的关注点略有不同,通过两个visit方法分别进行处理。CTO的Visitor中只关注工程师写的代码量,而经理只关注他的新产品数量。不同的visit函数会对元素进行不同的操作,而通过注入不同的Visitor又可以替换掉访问者的具体实现,使得对元素的操作变得更灵活,可扩展性更高,同时也消除了类型转换、if-else等“丑陋”的代码。
下面是客户端代码:

package com.guifa.patterndemo.visitorpattern;

public class Client {

    public static void main(String[] args) {
        // 构建报表
        BusinessReport businessReport = new BusinessReport();
        System.out.println("=====给CEO看的报表=====");
        // 设置访问者,这里是CEO
        businessReport.showReport(new CEOVisitor());
        System.out.println("=====给CFO看的报表=====");
        // 设置访问者,这里是CTO;
        businessReport.showReport(new CTOVisitor());
    }
}

客户端代码中,首先构建了一个报表对象,该对象中维护了所有员工的集合,然后通过报表类的showReport函数为Visitor对象提供一个访问接口,在这个函数中遍历所有的员工,然后调用员工的accept函数接受访问者的访问,每个访问者对不同类型的员工调用对应的visit函数实现不同的操作。

具体输出如下:
在这里插入图片描述
访问者模式最大的优点就是增加访问者非常容易,如果要增加一个访问者,你创建一个实现了Visitor接口的类,然后实现两个visit函数来对不同的元素进行不同的操作,从而达到数据对象与数据操作相分离的效果。如果不使用访问者模式,而又不想对不同的元素进行不同的操作,那么必定需要使用if-else和类型转换,这使得代码难以升级维护。此时,访问者模式的作用就体现出来了。

总结

访问者模式的有点:

  1. 各角色职责分离,符合单一职责原则。
  2. 具有优秀的扩展性。
  3. 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化。
  4. 灵活性。

访问者模式的缺点:

  1. 具体元素对访问者公布细节,违反了迪米特原则。
  2. 具体元素变更时导致修改成本大。
  3. 违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有依赖抽象。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值