【Java设计模式】行为型设计模式-访问者模式(二十一)

102 阅读7分钟

代码GitHub:github.com/lanjie6/Des…

访问者模式

  • 访问者模式是指将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。
  • 它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。

进一步阐述:

  • 在现实生活中,有些集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。
  • 例如:公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同。
  • 医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药。

访问者模式包含五种角色:

  • Visitor(抽象访问者角色):定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
  • ConcreteVisitor(具体访问者角色):实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  • Element(抽象元素角色):声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
  • ConcreteElement(具体元素角色):实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  • Object Structure(对象结构角色):是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

案例:财务都是有账本的,这个账本就可以作为一个对象结构,而它其中的元素有两种,收入和支出,这满足我们访问者模式的要求,即元素的个数是稳定的,因为账本中的元素只能是收入和支出,而查看账本的人可能有这样几种,比如老板,会计事务所的注会,财务主管等等。而这些人在看账本的时候显然目的和行为是不同的。其中账单Bill接口就是抽象元素角色,消费的账单ConsumeBill类和收入的账单IncomeBill类就是具体的元素角色,账单访问者AccountBookViewer接口就是抽象访问者角色,老板Boss类和会计师CPA类就是具体访问者角色,账本AccountBook类就是对象结构角色。

UML类图:

访问者模式.jpg

客户端Client类:

/**
 * 使用访问者模式的客户端
 */
public class Client {
​
    public static void main(String[] args) {
        AccountBook accountBook = new AccountBook();
        //添加两条收入
        accountBook.addBill(new IncomeBill(10000, "卖商品"));
        accountBook.addBill(new IncomeBill(12000, "卖广告位"));
        //添加两条支出
        accountBook.addBill(new ConsumeBill(1000, "工资"));
        accountBook.addBill(new ConsumeBill(2000, "材料费"));
​
        //创建两个访问者
        AccountBookViewer boss = new Boss();
        AccountBookViewer cpa = new CPA();
​
        //两个访问者分别访问账本
        accountBook.show(cpa);
        accountBook.show(boss);
​
        ((Boss) boss).getTotalConsume();
        ((Boss) boss).getTotalIncome();
    }
}

账本AccountBook类:

/**
 * 账本类(对象结构角色)
 */
public class AccountBook {
​
    //单子列表
    private List<Bill> billList = new ArrayList<>();
​
    //添加单子
    public void addBill(Bill bill) {
        billList.add(bill);
    }
​
    //供账本的查看者查看账本
    public void show(AccountBookViewer viewer) {
        for (Bill bill : billList) {
            bill.accept(viewer);
        }
    }
}

账单Bill接口:

/**
 * 账单接口(抽象元素角色)
 */
public interface Bill {
​
    /**
     * 对账单进行访问
     * @param viewer 访问者对象
     */
    void accept(AccountBookViewer viewer);
}

消费的账单ConsumeBill类:

/**
 * 消费的账单(具体元素角色)
 */
public class ConsumeBill implements Bill {
​
    //金额
    private double amount;
​
    //消费的事项
    private String item;
​
    public ConsumeBill(double amount, String item) {
        this.amount = amount;
        this.item = item;
    }
​
    public void accept(AccountBookViewer viewer) {
        viewer.viewConsumeBill(this);
    }
​
    public double getAmount() {
        return amount;
    }
​
    public String getItem() {
        return item;
    }
}

收入的账单IncomeBill类:

/**
 * 收入账单(具体元素角色)
 */
public class IncomeBill implements Bill {
​
    //金额
    private double amount;
​
    //收入的事项
    private String item;
​
    public IncomeBill(double amount, String item) {
        this.amount = amount;
        this.item = item;
    }
​
    public void accept(AccountBookViewer viewer) {
        viewer.viewIncomeBill(this);
    }
​
    public double getAmount() {
        return amount;
    }
​
    public String getItem() {
        return item;
    }
}

账单访问者AccountBookViewer接口:

/**
 * 账单访问者接口(抽象访问者角色)
 */
public interface AccountBookViewer {
​
    /**
     * 查看消费的账单
     */
    void viewConsumeBill(ConsumeBill bill);
​
    /**
     * 查看收入的账单
     */
    void viewIncomeBill(IncomeBill bill);
}

老板Boss类:

/**
 * 老板类(具体访问者角色)
 * 老板只关注一共花了多少钱以及一共收入多少钱,其余并不关心
 */
public class Boss implements AccountBookViewer{
​
    //总共收入的金额
    private double totalIncome;
​
    //总共消费的金额
    private double totalConsume;
​
    @Override
    public void viewConsumeBill(ConsumeBill bill) {
        totalConsume += bill.getAmount();
    }
​
    @Override
    public void viewIncomeBill(IncomeBill bill) {
        totalIncome += bill.getAmount();
    }
​
    public double getTotalIncome() {
        System.out.println("老板查看一共收入多少,数目是:" + totalIncome);
        return totalIncome;
    }
​
    public double getTotalConsume() {
        System.out.println("老板查看一共花费多少,数目是:" + totalConsume);
        return totalConsume;
    }
}

会计师CPA类:

/**
 * 会计师类(具体访问者角色)
 */
public class CPA implements AccountBookViewer {
​
    /**
     * 会计师在看账本时,如果是支出并且是工资,则需要看应该交的税交了没
     */
    @Override
    public void viewConsumeBill(ConsumeBill bill) {
        if (bill.getItem().equals("工资")) {
            System.out.println("会计师查看工资是否交个人所得税。");
        }
    }
​
    /**
     * 如果是收入,则所有的收入都要交税
     */
    @Override
    public void viewIncomeBill(IncomeBill bill) {
        System.out.println("会计师查看收入交税了没。");
    }
}

总结:

  • 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  • 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  • 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  • 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
  • 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了开闭原则。
  • 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  • 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

典型运用场景举例:

  • 访问者是一种集中规整模式,特别适合用于大规模重构的项目,在这一个阶段的需求已经非常清晰,原系统的功能点也已经明确,通过访问者模式可以很容易把一些功能进行梳理,达到最终目的功能集中化。
  • 简而言之,就是当对集合中的不同类型数据(类型数量稳定)进行多种操作时,使用访问者模式。

设计模式相关文章