访问者模式

简介

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

访问者模式(Visitor Pattern)是一种将数据结构与数据操作分离的设计模式。其基本思想是:对于系统中拥有固定类型数的对象结构(元素),可以通过在其内提供一个 accept 方法用来接受访问者对象的访问。不同的访问者对同一元素的访问内容不同,使得相同的元素集合可以产生不同的数据结果。解耦了数据结构与操作,且数据操作不会改变元素状态。

访问者模式 解耦数据结构与数据操作原理:元素内部提供一个 accept 方法,该方法可以接收不同的访问者对象,然后在内部将自己(元素)转发到接收到的访问者对象的 visit 方法内。访问者内部对应类型的 visit 方法就会得到回调执行,对元素进行操作。也就是通过两次动态分发(第一次是对访问者的分发accept,第二次是对元素的分发visit),才最终将一个具体的元素传递到一个具体的访问者。

访问者模式 核心:解耦数据结构与数据操作,使得对元素的操作具备优秀的扩展性。可以通过扩展不同的数据操作类型(访问者)实现对相同元素集的不同的操作。

主要解决

当系统中存在类型数目稳定(固定)的一类数据结构时,可以通过 访问者模式 方便地实现对该类型所有数据结构的不同操作,而又不会数据产生任何副作用(脏数据)。

简单理解:想对集合中的不同类型数据(类型数量稳定)进行多种操作,则使用 访问者模式

优缺点

优点

  • 解耦了数据结构与数据操作,使得操作集合可以独立变化;
  • 扩展性好:可以通过扩展访问者角色,实现对数据集的不同操作;
  • 元素类型无需一致,访问者均可操作;
  • 各角色职责分离,符合 单一职责原则

缺点

  • 无法增加元素类型:若系统数据结构对象易于变化,经常有新的数据对象增加进来,则访问者类必须增加对应元素类型的操作,违背了 开闭原则
  • 具体元素变更困难:具体元素增加属性,删除属性等操作会导致对应的访问者类需要进行相应的修改,尤其当有大量访问者类时,修改范围太大;
  • 违背 依赖倒置原则:为了达到”区别对待“,访问者依赖的是具体元素类型,而不是抽象;

使用场景

  • 数据结构稳定,作用于数据结构的操作经常变化的场景;
  • 需要数据结构与数据操作分离的场景;
  • 需要对不同数据类型(元素)进行操作,而不使用分支判断具体类型的场景;

模式讲解

首先来看下 访问者模式 的通用 UML 类图:

访问者模式

从 UML 类图中,我们可以看到,访问者模式 主要包含五种角色:

  • 抽象访问者(Visitor):接口或抽象类,该类地冠以了对每一个具体元素(Element)的访问行为visit,其参数就是具体的元素(Element)对象。理论上来说,Visitor 的方法个数与元素(Element)个数是相等的。如果元素(Element)个数经常变动,会导致 Visitor 的方法也要进行变动,此时,该情形并不适用 访问者模式
  • 具体访问者(ConcreteVisitor):实现对具体元素的操作;
  • 抽象元素(Element):接口或抽象类,定义了一个接受访问者访问的方法accept,表示所有元素类型都支持被访问者访问;
  • 具体元素(Concrete Element):具体元素类型,提供接受访问者的具体实现。通常的实现都为:visitor.visit(this)
  • 结构对象(ObjectStruture):该类内部维护了元素集合,并提供方法接受访问者对该集合所有元素进行操作;

以下是 访问者模式 的通用代码:

class Client {
    public static void main(String[] args) {
        ObjectStructure collection = new ObjectStructure();
        System.out.println("ConcreteVisitorA handle elements:");
        IVisitor visitorA = new ConcreteVisitorA();
        collection.accept(visitorA);
        System.out.println("------------------------------------");
        System.out.println("ConcreteVisitorB handle elements:");
        IVisitor visitorB = new ConcreteVisitorB();
        collection.accept(visitorB);

    }

    // 抽象元素
    interface IElement {
        void accept(IVisitor visitor);
    }

    // 具体元素
    static class ConcreteElementA implements IElement {

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

        public String operationA() {
            return this.getClass().getSimpleName();
        }
    }

    // 具体元素
    static class ConcreteElementB implements IElement {

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

        public int operationB() {
            return new Random().nextInt(100);
        }
    }

    // 抽象访问者
    interface IVisitor {
        void visit(ConcreteElementA element);

        void visit(ConcreteElementB element);
    }

    // 具体访问者
    static class ConcreteVisitorA implements IVisitor {
        @Override
        public void visit(ConcreteElementA element) {
            String result = element.operationA();
            System.out.println(String.format("result from %s: %s", element.getClass().getSimpleName(), result));
        }

        @Override
        public void visit(ConcreteElementB element) {
            int result = element.operationB();
            System.out.println(String.format("result from %s: %s", element.getClass().getSimpleName(), result));
        }
    }

    // 具体访问者
    static class ConcreteVisitorB implements IVisitor {
        @Override
        public void visit(ConcreteElementA element) {
            String result = element.operationA();
            System.out.println(String.format("result from %s: %s", element.getClass().getSimpleName(), result));
        }

        @Override
        public void visit(ConcreteElementB element) {
            int result = element.operationB();
            System.out.println(String.format("result from %s: %s", element.getClass().getSimpleName(), result));
        }
    }

    // 结构对象
    static class ObjectStructure {
        private List<IElement> mList = new ArrayList<>();

        {
            this.mList.add(new ConcreteElementA());
            this.mList.add(new ConcreteElementB());
        }

        public void accept(IVisitor visitor) {
            for (IElement element : this.mList) {
                element.accept(visitor);
            }
        }
    }
}

运行结果如下:

ConcreteVisitorA handle element:
result from ConcreteElementA: ConcreteElementA
result from ConcreteElementB: 58
------------------------------------
ConcreteVisitorB handle element:
result from ConcreteElementA: ConcreteElementA
result from ConcreteElementB: 83

举个例子

例子:假设现 NBA 某球队近期将对各个球员能力进行统计,统计表同时提供给教练和球队老板查看。假设教练只对球员身体素质,场均得分感兴趣,而老板对球员身价,粉丝数量感兴趣。请用代码进行实现。

分析:为了简单,我们假设支队球员 A 和球员 B 进行能力统计。则题目相当于是统计 A 和 B 的相关 数据 提交给教练和老板 查看。也就是对数据和对数据的操作,非常适合使用 访问者模式 进行实现。球员A 和 B 相当于元素(Element),教练和老板相当于访问者(Visitor),球队经理相当于结构对象,负责统计数据并提交。

具体代码如下:

class Client {
    public static void main(String[] args) {
        Manager manager = new Manager();

        System.out.println("Coach look up:");
        IViewer coach = new Coach();
        manager.show(coach);

        System.out.println("--------------------");
        System.out.println("Boss look up:");
        IViewer boss = new Boss();
        manager.show(boss);

    }

    static abstract class Player {
        // 姓名
        private String mName;
        // 身体素质
        private String mFitness;
        // 场均得分
        private int mScorePerGame;
        // 粉丝数量
        private int mFans;
        // 身价
        private int mSalary;

        public Player(String name, int salary, String fitness, int scorePerGame, int fans) {
            this.mName = name;
            this.mSalary = salary;
            this.mFitness = fitness;
            this.mScorePerGame = scorePerGame;
            this.mFans = fans;
        }

        public String getName() {
            return this.mName;
        }

        public String getFitness() {
            return this.mFitness;
        }

        public int getScorePerGame() {
            return this.mScorePerGame;
        }

        public int getFans() {
            return this.mFans;
        }

        public int getSalary() {
            return this.mSalary;
        }

        abstract void accept(IViewer viewer);
    }

    static class PlayerA extends Player {

        public PlayerA(String name, int salary, String fitness, int scorePerGame, int fans) {
            super(name, salary, fitness, scorePerGame, fans);
        }

        @Override
        public void accept(IViewer viewer) {
            viewer.show(this);
        }
    }

    static class PlayerB extends Player {

        public PlayerB(String name, int salary, String fitness, int scorePerGame, int fans) {
            super(name, salary, fitness, scorePerGame, fans);
        }

        @Override
        public void accept(IViewer viewer) {
            viewer.show(this);
        }
    }

    interface IViewer {
        void show(PlayerA player);

        void show(PlayerB player);
    }

    static class Coach implements IViewer {

        @Override
        public void show(PlayerA player) {
            this.getInterested(player);
        }

        @Override
        public void show(PlayerB player) {
            this.getInterested(player);
        }

        private void getInterested(Player player) {
            String name = player.getName();
            String fitness = player.getFitness();
            int scorePerGame = player.getScorePerGame();
            int fans = player.getFans();
            System.out.println(String.format("%s [body: %s,scorePerGame: %d]", name, fitness, scorePerGame));
        }
    }

    static class Boss implements IViewer {

        @Override
        public void show(PlayerA player) {
            this.getInterested(player);
        }

        @Override
        public void show(PlayerB player) {
            this.getInterested(player);
        }

        private void getInterested(Player player) {
            String name = player.getName();
            int salary = player.getSalary();
            int fans = player.getFans();
            System.out.println(String.format("%s [salary: $%d ,fans: %d]", name, salary, fans));
        }
    }

    static class Manager {
        private List<? extends Player> mPlayers = Arrays.asList(
            new PlayerA("John", 2000000, "good", 20, 500000),
             new PlayerA("Bill", 20000000, "excellent", 30, 15000000));

        public void show(IViewer viewer) {
            for (Player player : this.mPlayers) {
                player.accept(viewer);
            }
        }
    }
}

运行结果如下:

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

推荐阅读更多精彩内容