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

作者: 风吹稻子 | 来源:发表于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:很感谢博主写的文章,看懂了访问者模式是什么。不过我有一个问题,比如上面的账单的例子当中,为什么不直接让账簿提供一个方法返回账簿里面的收入和支出数据,访问者直接访问这个方法返回的数据就好了?
        感觉使用上面这种设计模式反而使得逻辑变得更加复杂了(从账簿到收支数据再到访问者饶了一圈),而且拓展性也没有太大的提高(访问者在使用这种模式之前同样可以灵活变动,在使用这种模式之后数据同样没有实现灵活变动)
        风吹稻子:@梦中真 谢谢你的意见
        梦中真:写的不错,挺好理解的
        我觉得
        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