《Android源码设计模式解析与实战》-最复杂的设计模式-访问者模式

访问者模式 定义

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

访问者模式使用场景

     1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
     2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。

UML类图

角色介绍

     (1) visitor:接口或者抽象类-它定义了对每个元素访问的行为,他的参数就是访问的元素,理论上讲,该类的方法数量和元素数量是一致的。因此访问者模式需要要求元素的类簇是要稳定的,如果经常添加、删除元素类、必然会频繁的修改访问者类。如果出现这种情况,说明场景不适合访问者模式。
    (2)ConcreateVisitor :具体的访问者,他需要给每个元素类访问时产生的具体行为。
    (3)Element:元素类,他要定义一个接受访问的方法(accept),其意义是指每个元素都应该可以被访问者访问。
    (4)ConcreateElement:具体的元素类,他提供具体的访问方法实现。一般由访问者提供
    (5)ObjectStructure:定义对象结构,对象结构是一个抽像表述,它内部管理了元素类,并且可以迭代这些对象共访问者访问。

访问者模式简单实例

     在年终的时候,公司都会对员工的业绩进行考核。但是不同领域的管理人员对员工的评定是不一样的。为了简单说明问题,我们把员工分成工程师和经理,评定员工分成CEO 和CTO 。我们假定CTO只关注工程师代码量,经理新产品数量,而CEO关注的是工程师的KPI和经理的KPI以及产品的数量。
     从中可以看出CEO 和CTO的关注点是不一样的,这就要对不同的员工进行不同的处理,这时访问者模式就排上用场了。


/**
 * 基类 - 员工
 * @author Zero
 */
public abstract class Staff {
    public String name;
    public int kpi;

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

    /**
     * 接受上级访问(查岗)
     * 
     * @param visitor
     */
    public abstract void accept(Visitor visitor);

}

员工提供了一个accept方法 提供访问者访问。具体访问逻辑由子类实现。

/**
 * 员工 - 工程师
 * @author Zero
 */
public class Engineer extends Staff {

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

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

    /**
     * 工程师一年的代码量
     * @return
     */
    public int getCodeLines() {
        return new Random().nextInt(10000 * 10);
    }

}

/**
 * 员工 - 经理
 * @author Zero
 */
public class Manager extends Staff {

    int products;

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

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

    /**
     * 一年内做的产品数量
     * 
     * @return
     */
    public int getProducts() {
        return products;
    }

}

工程师和经理分别提供不同的访问业绩方法。并把访问逻辑委托到访问者去实现。

/**
 * 统计报表 - 业务报告
 * @author Zero
 */
public class BusinessReport {
    List<Staff> mStaffs = new LinkedList<>();

    public BusinessReport() {
        mStaffs.add(new Manager("经理 - 王"));
        mStaffs.add(new Manager("经理 - 赵"));
        mStaffs.add(new Engineer("工程师 - 李"));
        mStaffs.add(new Engineer("工程师 - 张"));
        mStaffs.add(new Engineer("工程师 - 钱"));
        mStaffs.add(new Engineer("工程师 - 马"));
    }

    /**
     * 为公司领导展示报表
     */
    public void showReprot(Visitor visitor) {
        for (Staff staff : mStaffs) {
            staff.accept(visitor);
        }
    }
}

下面看看访问者的方法定义

public interface Visitor {

    /**
     * 访问工程师类型
     * @param engineer
     */
    public void visit(Engineer engineer) ;

    /**
     * 访问经理类型
     * @param manager
     */
    public void visit(Manager manager) ;

}

提供了一个重载的访问方法,目的就是区别对待 、差异化处理不同的元素。

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.products);
    }

}

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.products);
    }

}

如果我们需要使用一个访问方法 的方式去做,我们的代码大致是这样子的:

public void visit(Staff staff) {
    if(staff instanceof Engineer) {
        //做一些操作
    }else {
    //做一些操作
    }
}

如果类型再多了,那将会出现大量的if - else 这样代码逻辑就会越来越复杂。可以看出使用访问者模式,使代码简洁、扩展性更强了。
客户端代码:

public class Client {
    public static void main(String[] args) {
        BusinessReport report = new BusinessReport();
        System.out.println("============  给CEO看的报表  ============");
        report.showReprot(new CEOVisitor());
        System.out.println("============  给CTO看的报表  ============");
        report.showReprot(new CTOVisitor());
    }
}

UML类图

优缺点评定

优点: 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。
缺点: 1、具体元素对访问者公布细节,违反了迪米特原则。 2、具体元素变更比较困难。 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容