1. 优享JAVA首页
  2. Java
  3. 设计模式

设计模式之访问者模式

访问者模式(Visitor),将对相对固定的数据结构的操作封装,实现对数据结构的操作与数据结构本身分离,方便扩展对数据结构的新操作。

这个模式的基本想法:首先我们拥有一个由许多对象构成的对象结构, 这些对象的类都拥有一个 accept 方法用来接受访问者对象;

访问者是一个接口,它拥有一个 visit 方法,这个方法对访问到的对象结构中不同类型的元素作出不同的反应;在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都使用 accept 方法。

在每一个元素的 accept 方法中回调访问者的 visit 方法,从而使访问者得以处理对象结构的每一个元素。我们可以针对对象结构设计不同的访问者类来完成不同的操作。

下面是一个示例

public class VisitorDemo {
    static public void main(String[] args) {
        Car car = new Car();
        Visitor visitor = new PrintVisitor();
        car.accept(visitor);
    }
}

// 访问者接口,提供访问对象的方法,利用java重载特性,根据参数的不同执行不同的逻辑
interface Visitor {
    void visit(Wheel wheel);
    void visit(Engine engine);
    void visit(Body body);
    void visit(Car car);
}

// 数据结构,车轮
class Wheel {
    private String name;
    Wheel(String name) { this.name = name; }
    String getName() { return this.name; }
    // 接受访问的方法
    void accept(Visitor visitor) { visitor.visit(this); }
}

// 数据结构  汽车引擎
class Engine {
    // 接受访问的方法
    void accept(Visitor visitor) { visitor.visit(this); }
}

//数据结构 汽车主体
class Body {
    // 接受访问的方法
    void accept(Visitor visitor) { visitor.visit(this); }
}

//数据结构 汽车
class Car {
    private Engine engine = new Engine();
    private Body body = new Body();
    private Wheel[] wheels
            = {new Wheel("front left"), new Wheel("front right"),
            new Wheel("back left"), new Wheel("back right")};

    // 接受访问的方法
    void accept(Visitor visitor) {
        visitor.visit(this);
        engine.accept(visitor);
        body.accept(visitor);
        for (int i = 0; i < wheels.length; ++i)
            wheels[i].accept(visitor);
    }
}

//通过实现Visitor接口,可以设计不同的访问者类完成不同的操作,这里PrintVisitor类主要用与打印
class PrintVisitor implements Visitor {
    //处理汽车车轮类的visit方法
    public void visit(Wheel wheel) { System.out.println("Visiting " + wheel.getName() + " wheel"); }
    //处理汽车引擎类的visit方法
    public void visit(Engine engine) { System.out.println("Visiting engine"); }
    //处理汽车主体类的visit方法
    public void visit(Body body) { System.out.println("Visiting body"); }
    //处理汽车类的visit方法
    public void visit(Car car) { System.out.println("Visiting car"); }
}

Visitor模式提供了一种multi-dispatch(多分派) 中的double dispatch(双分派)的实现方式, 扩展对于像Java,C++这样的单分派语言的一种模拟 multi-dispatch(多分派) 技术

什么是双分派和多分派

双分派是多分派的特例,实际上双分派是一种很经典的技术,但是当前的主流的面向对象程序设计语言(例如C++/Java/C#等)都并不支持双分派或多分派,仅仅支持单分派(single dispatch)。

单分派的含义比较好理解,单分派就是说我们在选择一个方法的时候仅仅需要根据消息接收者(receiver)的运行时类型。实际上这也就是我们经常提到的多态的概念。举一个简单的例子,我们有一个基类A,A有一个虚方法f(可被子类override),D1和D2是A的两个子类,在D1和D2中我们覆写(override)了方法f。这样我们对消息f的调用,需要根据接收者A或者A的子类D1/D2的具体型别才可以确定具体是调用A的还是D1/D2的f方法。

双分派则在选择一个方法的时候,不仅仅要根据消息接收者(receiver)
的运行时类型,还要根据参数的运行时类型。当然如果所有参数都考虑的话就是多分派。也举一个简单的例子,同于上面单分派中例子,A的虚方法f带了一个C型别的参数,C也是一个基类,C有也有两个具体子类E1和E2。这样,当我们在调用消息f的时候,我们不但要根据接收者的具体型别(A、D1、D2),还要根据参数的具体型别(C、E1、E2),才可以最后确定调用的具体是哪一个方法f。

通过下面示例继续了解访问者模式

首先定义好数据结构,在这个示例中,工厂下有仓库和商品两个子类,所以我们将数据结构抽象为下面三个类

/** 数据结构基类,拥有公共属性和方法 */
public abstract class Flow {
    private String name;

    public abstract void accept(FlowVisitor visitor);

    public void setName(String name) { this.name = name; }
    public String getName() { return name; }
}
/** 商品信息 */
public class Goods extends Flow {
    public Goods(String name) { super.setName(name); }

    @Override
    public void accept(FlowVisitor visitor) {
        visitor.visit(this);
    }
}
/** 仓库信息 */
public class Warehouse extends Flow {
    private List<Flow> children;

    public Warehouse(String name) {
        children = new ArrayList<>();
        setName(name);
    }

    @Override
    public void accept(FlowVisitor visitor) {
        visitor.visit(this);
        for (Flow child : children) {
            child.accept(visitor);
        }
    }

    /** 添加商品 */
    public void add(Flow flow) { children.add(flow); }
}

接下来是Visitor的抽象以及两个实现类,实际上Visitor所做的操作即封装上述两个数据结构对象

public interface FlowVisitor {
    void visit(Warehouse warehouse);
    void visit(Goods goods);
}
public class SearchVisitor implements FlowVisitor{

    private Logger LOGGER = LoggerFactory.getLogger(WarehouseVisitor.class);
    private Pattern fileNamePattern;

    public SearchVisitor(String fileNamePattern) {
        this.fileNamePattern = Pattern.compile(fileNamePattern);
    }

    @Override
    public void visit(Warehouse warehouse) {
        if (fileNamePattern.matcher(warehouse.getName()).find()) {
            LOGGER.info(warehouse.getName());
        }
    }

    @Override
    public void visit(Goods goods) {
        if (fileNamePattern.matcher(goods.getName()).find()) {
            LOGGER.info("搜索商品:"+goods.getName());
        }
    }
}
public class WarehouseVisitor implements FlowVisitor {
    private Logger LOGGER = LoggerFactory.getLogger(WarehouseVisitor.class);

    @Override
    public void visit(Warehouse warehouse) {
        LOGGER.info(warehouse.getName());
    }

    @Override
    public void visit(Goods goods) {
        LOGGER.info(goods.getName());
    }
}

下面main方法中,传入不同类型的Visitor实现不同的功能

public class App {
    public static Warehouse create() {
        // 实例化一批商品
        Warehouse warehouse = new Warehouse("[1_仓库]");
        warehouse.add(new Goods("[1_仓库]-1商品"));
        warehouse.add(new Goods("[1_仓库]-2商品"));
        Warehouse area = new Warehouse("[1_仓库]-[1_库区]");
        warehouse.add(area);
        return warehouse;
    }

    public static void main(String[] args) {
        Warehouse warehouse = create();
        warehouse.accept(new WarehouseVisitor());
        warehouse.accept(new SearchVisitor("2商品"));
    }
}

执行结果如下

[main] INFO com.company.goods.WarehouseVisitor - [1_仓库]
[main] INFO com.company.goods.WarehouseVisitor - [1_仓库]-1商品
[main] INFO com.company.goods.WarehouseVisitor - [1_仓库]-2商品
[main] INFO com.company.goods.WarehouseVisitor - [1_仓库]-[1_库区]
[main] INFO com.company.goods.WarehouseVisitor - 搜索商品:[1_仓库]-2商品

访问者模式使用了多态性来实现过程的动态绑定,相较于其他设计模式要复杂一些。访问者模式同时利用了overrideoverload实现了双分派,及同时利用方法的静态绑定和动态绑定,通过这样的设计实现异常强大的特性。对于数据结构的操作可以自由扩充,只需实现FolwVisitor接口,添加操作数据结构的新功能,而不用对数据结构本身做任何改动。

不过这个模式也有一些缺点,首先最明显的是它的复杂度。一旦处理的业务逻辑变的复杂,维护起来也是相当有难度的。其次是如果对数据结构本身的类继承体系进行大改动的话,相应的其他改动也是非常大的。

所以访问者模式适用于在数据结构比较稳定的实际应用中。

原创文章,作者:Craig,如若转载,请注明出处:https://www.goodlymoon.com/archives/1309.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注

QR code