访问者模式(Visitor)

访问者模式是什么?

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

适用场合

当一个对象结构包括很多类对象,它们有不同的接口,而系统要求这些对象实施一些依赖于某具体类的操作时,就可以使用访问者模式

结构图

image.png

通过上图可以看到他有如下角色:

  • 抽象访问者(Visitor)角色:定义接口,声明一个或多个访问操作。
  • 具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作。
  • 抽象元素(Visitable/Element)角色:声明一个接受操作,接受一个访问者(Visitor)对象作为一个参数。
  • 具体元素结点(ConcreteElement)角色:实现抽象元素(Visitable/Element)所规定的accept操作。
  • 数据结构对象(Object Structure)角色:可以遍历结构中的所有元素,提供一个接口让访问者对象都可以访问每一个元素。

优点:

使用访问者模式,对于原来的类层次增加新的操作只需要实现一个具体访问者角色,而不必改变整个类层次。每个具体的访问者角色都对应于一个相关操作。

缺点:

不适合具体元素角色经常发生变化的情况。每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。也就是说,在元素类数目不确定的情况下,应该慎用访问者模式。

具体案例1

小巩要设计超市收银系统,碰到了困难,有的按重量计价,有的按物件计价,还有其他计价方式。
这时候可以使用访问者模式。

【1】首先设计抽象元素(Visitable/Element),提供访问者需要的accept方法

public interface Goods {
    double accept(Visitor visitor);
}

【2】设计具体元素类,包括猪肉类、酒类、电视机类

public class Pig implements Goods {
    public double accountByUnit() {
        System.out.println("猪肉按斤计价,购买的数量为:" + getCount() + "斤,购买的单价为:" + getPrice() + ",总价为:" + getCount() * getPrice());
        return getCount() * getPrice();
    }
    public double accept(Visitor visitor) {
        return visitor.visit(this);
    }

    public float getCount(){ return count; }

    public void setCount(float count){ this.count = count; }

    public float getPrice(){ return price; }

    public void setPrice(float price){ this.price = price; }

    private float count;
    private float price;
}

public class Wine implements Goods {
    public double accountByBottle() {
        System.out.println("酒按瓶计价,购买的数量为:" + getCount() + "瓶,购买的单价为:" + getPrice() + ",总价为:" + getCount() * getPrice());
        return getCount() * getPrice();
    }

    public double accept(Visitor visitor) {
        return visitor.visit(this);
    }

    public int getCount(){
            return count;
        }

    public void setCount(int count){
            this.count = count;
        }

    public float getPrice(){ return price; }

    public void setPrice(float price){ this.price = price; }

    private int count;
    private float price;
}

public class Television implements Goods {
    public double accountByPiece() {
        System.out.println("电视按台计价,购买的数量为:" + getCount() + "台,购买的单价为:" + getPrice() + ",总价为:" + getCount() * getPrice());
        return getCount() * getPrice();
    }
    public double accept(Visitor visitor) {
        return visitor.visit(this);
    }

    public int getCount(){
            return count;
        }

    public void setCount(int count){
            this.count = count;
        }

    public float getPrice(){ return price; }

    public void setPrice(float price){ this.price = price; }

    private int count;
    private float price;
}

【3】需要一个购物车类(Object Structure)收集要买的具体对象

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

public class ShoppingCart {
    public void add(Object object) {
        list.add(object);
    }

    public void remove(Object object) {
        list.remove(object);
    }

    public List getList() {
        return list;
    }

    private List list = new ArrayList();
}

【4】Visitor接口


public interface Visitor {
    double visit(Wine wine);
    double visit(Pig pig);
    double visit(Television television);
}

【5】Visitor实现类

public class VisitorImpl implements Visitor {
    public double visit(Wine wine) {
        return wine.accountByBottle();
    }
    public double visit(Pig pig) {
        return pig.accountByUnit();
    }
    public double visit(Television television) {
        return television.accountByPiece();
    }
}

【6】设计收银机类可以调用具体物件供访问的收费方法

public class AccountMachine {
    private double amt;
    public void account(List list) {
        Visitor visitor = new VisitorImpl();
        for (int i = 0; i < list.size(); i++) {
            amt += ((Goods)list.get(i)).accept(visitor);
        }
    }
    public double getAmt() {
        return amt;
    }
}

【7】客户端调用的代码(Client)

public class Client {
    public static void main(String[] argv) {
        Wine wine = new Wine();
        wine.setCount(10);
        wine.setPrice(20f);

        Pig pig = new Pig();
        pig.setCount(10f);
        pig.setPrice(15f);

        Television television = new Television();
        television.setCount(1);
        television.setPrice(2000f);

        ShoppingCart shoppingCart = new ShoppingCart();
        shoppingCart.add(wine);
        shoppingCart.add(pig);
        shoppingCart.add(television);

        AccountMachine accountMachine = new AccountMachine();
        accountMachine.account(shoppingCart.getList());
        System.out.println("本次购物车内所有物品的总价为:" + accountMachine.getAmt());
    }
}

具体案例2

针对同一个数据接口有不同的Visitor

image.png
interface CarElement {
    void accept(CarElementVisitor visitor);
}

interface CarElementVisitor {
    void visit(Body body);
    void visit(Car car);
    void visit(Engine engine);
    void visit(Wheel wheel);
}

class Car implements CarElement {
    CarElement[] elements;

    public Car() {
        this.elements = new CarElement[] {
            new Wheel("front left"), new Wheel("front right"),
            new Wheel("back left"), new Wheel("back right"),
            new Body(), new Engine()
        };
    }

    public void accept(final CarElementVisitor visitor) {
        for (CarElement elem : elements) {
            elem.accept(visitor);
        }
        visitor.visit(this);
    }
}

class Body implements CarElement {
    public void accept(final CarElementVisitor visitor) {
        visitor.visit(this);
    }
}

class Engine implements CarElement {
    public void accept(final CarElementVisitor visitor) {
        visitor.visit(this);
    }
}

class Wheel implements CarElement {
    private String name;

    public Wheel(final String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void accept(final CarElementVisitor visitor) {
        /*
         * accept(CarElementVisitor) in Wheel implements
         * accept(CarElementVisitor) in CarElement, so the call
         * to accept is bound at run time. This can be considered
         * the *first* dispatch. However, the decision to call
         * visit(Wheel) (as opposed to visit(Engine) etc.) can be
         * made during compile time since 'this' is known at compile
         * time to be a Wheel. Moreover, each implementation of
         * CarElementVisitor implements the visit(Wheel), which is
         * another decision that is made at run time. This can be
         * considered the *second* dispatch.
         */
        visitor.visit(this);
    }
}

class CarElementDoVisitor implements CarElementVisitor {
    public void visit(final Body body) {
        System.out.println("Moving my body");
    }

    public void visit(final Car car) {
        System.out.println("Starting my car");
    }

    public void visit(final Wheel wheel) {
        System.out.println("Kicking my " + wheel.getName() + " wheel");
    }

    public void visit(final Engine engine) {
        System.out.println("Starting my engine");
    }
}

class CarElementPrintVisitor implements CarElementVisitor {
    public void visit(final Body body) {
        System.out.println("Visiting body");
    }

    public void visit(final Car car) {
        System.out.println("Visiting car");
    }

    public void visit(final Engine engine) {
        System.out.println("Visiting engine");
    }

    public void visit(final Wheel wheel) {
        System.out.println("Visiting " + wheel.getName() + " wheel");
    }
}

public class VisitorDemo {
    public static void main(final String[] args) {
        final Car car = new Car();

        car.accept(new CarElementPrintVisitor());
        car.accept(new CarElementDoVisitor());
    }
}

参考文档

https://en.wikipedia.org/wiki/Visitor_pattern
http://blog.csdn.net/zhi_fu/article/details/77725864
http://alaric.iteye.com/blog/1942517

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

推荐阅读更多精彩内容