访问不怕,就怕理解访问者模式

作者: 风吹稻子 | 来源:发表于2017-04-30 16:41 被阅读3513次

1.什么是访问者模式?

比如我有一个账单,账单有收入,支出两个固定方法。但是访问账单的人不确定,有可能是一个或者多个。

2.访问者模式有两个特点

  1. 一般被访问的东西所持有的方法是固定的,就像账单只有收入和支出两个功能。而访问者是不固定的。

  2. 数据操作与数据结构相分离:频繁的更改数据,但不结构不变。比如:虽然每一天账单的数据都会变化(数据变化),但是只有两类数据,就是支出和收入(结构不变)。

简化如下图:

访问者模式.png

例子:

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

/**
 * 创建一个账单接口,有接收访问者的功能
 * 
 * @author hgx
 *
 */
interface Bill {
    void accept(AccountBookView viewer);
}

/**
 * 消费单子
 * 
 * @author hgx
 *
 */
class ConsumerBill implements Bill {

    private String item;
    private double amount;

    public ConsumerBill(String item, double amount) {
        this.item = item;
        this.amount = amount;
    }

    public void accept(AccountBookView viewer) {
        viewer.view(this);
    }

    public String getItem() {
        return item;
    }

    public double getAmount() {
        return amount;
    }

}

/**
 * 收入单子
 * 
 * @author hgx
 *
 */
class IncomeBill implements Bill {

    private String item;
    private double amount;

    public IncomeBill(String item, double amount) {
        this.item = item;
        this.amount = amount;
    }

    public void accept(AccountBookView viewer) {
        viewer.view(this);
    }

    public String getItem() {
        return item;
    }

    public double getAmount() {
        return amount;
    }

}

/**
 * 访问者接口
 * 
 * @author hgx
 *
 */
interface AccountBookView {
    // 查看消费的单子
    void view(ConsumerBill consumerBill);

    // 查看收入单子
    void view(IncomeBill incomeBill);
}

// 老板类:访问者是老板,主要查看总支出和总收入
class Boss implements AccountBookView {

    private double totalConsumer;
    private double totalIncome;

    // 查看消费的单子
    public void view(ConsumerBill consumerBill) {
        totalConsumer = totalConsumer + consumerBill.getAmount();
    }

    // 查看收入单子
    public void view(IncomeBill incomeBill) {
        totalIncome = totalIncome + incomeBill.getAmount();
    }

    public void getTotalConsumer() {
        System.out.println("老板一共消费了" + totalConsumer);
    }

    public void getTotalIncome() {
        System.out.println("老板一共收入了" + totalIncome);
    }
}

/**
 * 会计类:访问者是会计,主要记录每笔单子
 * 
 * @author hgx
 *
 */

class CPA implements AccountBookView {

    int count = 0;

    // 查看消费的单子
    public void view(ConsumerBill consumerBill) {
        count++;
        if (consumerBill.getItem().equals("消费")) {
            System.out.println("第" + count + "个单子消费了:" + consumerBill.getAmount());
        }
    }
    // 查看收入单子

    public void view(IncomeBill incomeBill) {

        if (incomeBill.getItem().equals("收入")) {
            System.out.println("第" + count + "个单子收入了:" + incomeBill.getAmount());
        }
    }

}

/**
 * 账单类:用于添加账单,和为每一个账单添加访问者
 * 
 * @author hgx
 *
 */
class AccountBook {

    private List<Bill> listBill = new ArrayList<Bill>();

    // 添加单子
    public void add(Bill bill) {
        listBill.add(bill);
    }

    // 为每个账单添加访问者
    public void show(AccountBookView viewer) {
        for (Bill b : listBill) {
            b.accept(viewer);
        }
    }
}

/*
 *测试类
 */
public class Test {

    public static void main(String[] args) {
        // 创建消费和收入单子
        Bill consumerBill = new ConsumerBill("消费", 3000);
        Bill incomeBill = new IncomeBill("收入", 5000);
        Bill consumerBill2 = new ConsumerBill("消费", 4000);
        Bill incomeBill2 = new IncomeBill("收入", 8000);
        // 添加单子
        AccountBook accountBook = new AccountBook();
        accountBook.add(consumerBill);
        accountBook.add(incomeBill);
        accountBook.add(consumerBill2);
        accountBook.add(incomeBill2);
        // 创建访问者
        AccountBookView boss = new Boss();
        AccountBookView cpa = new CPA();

        // 接受访问者
        accountBook.show(boss);
        accountBook.show(cpa);
        // boss查看总收入和总消费
        ((Boss) boss).getTotalConsumer();
        ((Boss) boss).getTotalIncome();

    }

}

测试结果:

第1个单子消费了:3000.0
第1个单子收入了:5000.0
第2个单子消费了:4000.0
第2个单子收入了:8000.0
老板一共消费了:7000.0
老板一共收入了:13000.0

本文按照个人理解,全部通俗化解释,如有错误希望指出 。

相关文章

网友评论

  • 简书极简:AccountBookView boss=new boss();
    Bill bill=new ConsumeBill();
    访问者和被访问者都有了,然后show函数依次bill.accept(boss);而accept函数实现的就是boss.view(bill);
    所以,为什么不直接 boss.view(bill)呢?目前还没有接触到访问者的真实使用场景,所以搞不清楚,真心请问博主解答!
    风吹稻子:@简书极简 你去看账单,首先账单得允许你去看,也就是accept(),接受之后,你才能boss.view()。如果你直接boss.view(bill),那就是你还没有获得账单的允许,就强制去看。另外一个就是,人具有查看的行为,也就是view()。账单有展示的功能,也就是show()。总结就是:各司其职
  • 7e07286d9375:写得不错 不过我有一个疑问 就是在测试类里面 ((Boss) boss).getTotalConsumer();
    ((Boss) boss).getTotalIncome(); 这里为何还用强转呢 作为一个模式来说有点生硬!另外 访问者的定义也有点疑惑 为何要声明为具体的Bill呢

    /**
    * 访问者接口
    *
    * @Author hgx
    *
    */
    interface AccountBookView {
    // 查看消费的单子
    void view(ConsumerBill consumerBill);

    // 查看收入单子
    void view(IncomeBill incomeBill);
    }
    皮乐皮儿:@风吹稻子 我觉得可以用面向协议的方式去定,这样就不用明确指出到底是哪个,只要boss和cpa能够遵守协议就行了
    风吹稻子:@开门见山_6c1a 关于boss的问题,因为接口中没有getTotalConsumer()方法,而boss实例化的时候,是父类引用子类对象,所以只有强转之后,才能使用Boss自己的方法。对于第二个问题,当然也不必写死,灵活运用就好。
  • JBD:很感谢博主写的文章,看懂了访问者模式是什么。不过我有一个问题,比如上面的账单的例子当中,为什么不直接让账簿提供一个方法返回账簿里面的收入和支出数据,访问者直接访问这个方法返回的数据就好了?
    感觉使用上面这种设计模式反而使得逻辑变得更加复杂了(从账簿到收支数据再到访问者饶了一圈),而且拓展性也没有太大的提高(访问者在使用这种模式之前同样可以灵活变动,在使用这种模式之后数据同样没有实现灵活变动)
    风吹稻子:@梦中真 谢谢你的意见
    3e79b61339ba:写的不错,挺好理解的
    我觉得
    AccountBookView boss = new Boss();
    AccountBookView cpa = new CPA();
    应该换成
    Boss boss = new Boss();
    CPA cpa = new CPA();
    这样既不用强转,又具有可读性
    风吹稻子:@JBD 我个人认为,首先就面向对象来说,账单只有item和Amount这两个属性,而获取支出或收入是访问者才具有的行为。然后就是如果放到账单里的话,就是公有的,也就是说所有访问者都能获取到,但是作为会计,可能她只想添加账单。所以,访问者不同,行为就不同。而账单不管访问者做什么,它只关注自己的明细记录。谢谢提问,欢迎探讨!😊

本文标题:访问不怕,就怕理解访问者模式

本文链接:https://www.haomeiwen.com/subject/gnjxtxtx.html