访问者模式

335 阅读5分钟

前言

访问者模式是行为型设计模式。行为型设计模式主要的关注点是对象内部算法及对象之间的职责和分配,比如,具体实现算法、选择策略、状态变化等抽象概念。

行为型模式还可以分为以下两种。

  • 类行为型模式:使用继承的方式来关联不同类之间的行为。

  • 对象行为型模式:使用组合或聚合方式来分配不同类之间的行为。

目录

一、定义

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

官方定义,刚开始的时候还是需要看代码才能理解,其实就是新建一个类来封装对其他类的操作,例如获取其他类的数据等等,但不改变其他类的数据结构。

二、模式原理分析

先上模板代码,再举例

//1.抽象元素
public abstract class Element{
    //定义业务逻辑
    public abstract void doSomething();
    //允许谁来访问
    public abstract void accept(IVisitor visitor);
}
//2.具体元素
public abstract ConcreteElement1 extend Element{
    //完善业务逻辑
    public void accept(IVisitor visitor){
        visitor.visit(this);//将自己传递给visitor类
    }
}
public abstract ConcreteElement2 extend Element{
    //完善业务逻辑
    public void accept(IVisitor visitor){
        visitor.visit(this);//将自己传递给visitor类
    }
}
//3.抽象访问者
public interface IVisitor{
    //可以访问哪些对象
    public void visit(ConcreteElement1 el1);
    public void visit(ConcreteElement2 el2);
}
//4.具体的访问者
public class Visitor implements IVisitor{
    //访问el1元素
    public void visit(ConcreteElement1 el1){
        el1.doSomething();
    }
    //访问el2元素
    public void visit(ConcreteElement2 el2){
        el2.doSomething();
    }
}我们再来看下简单例子,通过不同路由访问不同操作系统

假设你是一家路由器软件的生产商,接了很多家不同硬件品牌的路由器的软件需求(比如,D-LinkTP-Link),这时你需要针对不同的操作系统(比如,LinuxWindows)做路由器发送数据的兼容功能

//1.访问角色类
public interface Router {
    void sendData(char[] data);
    void accept(RouterVisitor v);
}
//2.根据不同型号路由器具体实现对应功能
public class DLinkRouter implements Router{

    @Override
    public void sendData(char[] data) {
    }

    @Override
    public void accept(RouterVisitor v) {
        v.visit(this);
    }
}
public class TPLinkRouter implements Router {
    @Override
    public void sendData(char[] data) {
    }
    @Override
    public void accept(RouterVisitor v) {
        v.visit(this);
    }

}
//3.访问者类  用于给不同路由器提供访问的入口点
public interface RouterVisitor {
    void visit(DLinkRouter router);
    void visit(TPLinkRouter router);
}

public class LinuxRouterVisitor implements RouterVisitor{

    @Override
    public void visit(DLinkRouter router) {
        System.out.println("=== DLinkRouter Linux visit success!");
    }

    @Override
    public void visit(TPLinkRouter router) {
        System.out.println("=== TPLinkRouter Linux visit success!");
    }

}

public class WindowsRouterVisitor implements RouterVisitor{

    @Override
    public void visit(DLinkRouter router) {
        System.out.println("=== DLinkRouter Windows visit success!");
    }

    @Override
    public void visit(TPLinkRouter router) {
        System.out.println("=== DLinkRouter Windows visit success!");
    }

}
//4.场景类
public class Client {

    public static void main(String[] args) {
        LinuxRouterVisitor linuxRouterVisitor = new LinuxRouterVisitor();
        WindowsRouterVisitor windowsRouterVisitor = new WindowsRouterVisitor();

        DLinkRouter dLinkRouter = new DLinkRouter();
        dLinkRouter.accept(linuxRouterVisitor);
        dLinkRouter.accept(windowsRouterVisitor);

        TPLinkRouter tpLinkRouter = new TPLinkRouter();
        tpLinkRouter.accept(linuxRouterVisitor);
        tpLinkRouter.accept(windowsRouterVisitor);
    }
}

//输出结果
=== DLinkRouter Linux visit success!
=== DLinkRouter Windows  visit success!
=== TPLinkRouter Linux visit success!
=== DLinkRouter Windows  visit success!

不同型号的路由器可以在运行时动态添加(第一次分派)accept,对于不同的操作系统来说,路由器可以动态地选择适配(第二次分派)visit,整个过程完成了两次动态绑定。

单分派

  • 单分派语言处理一个操作是根据请求者的名称和接受到的参数决定的,在Java中有静态绑定和动态绑定之说,它的实现是依据重载和覆写来实现的。

  •   //一个演员和可扮演很多角色
      public interface Role{}
      public class GongFuRole implements Role{}
      public class IdiotRole implements Role{}
      //青年演员和老年演员
      public abstract class AbsActor{
      public void act(Role role){
         //演员可以扮演所有角色
         System.out.println("演员可以扮演所有角色");
      }
      public void act(GongFuRole role){
          System.out.println("年轻人最喜欢演功夫角色了");
      }}
      public class YoungActor extend AbsActor{
      
      public void act(GongFuRole role){        System.out.println("年轻人最喜欢演功夫角色了");
      }}
      public class OldActor extend AbsActor{
      public void act(GongFuRole role){
        System.out.println("老年人不能演功夫角色!!!!");
      }}
      //场景类
      AbsActor actor = new OldActor();
      Role role = new GongFuRole();
      actor.act(role);
      actor.act(new GongFuRole());
      //输出
      演员可以扮演所有角色
      老年人不能演功夫角色
      
    
  •  重载就是在编译器就决定了调用哪个方法,它是根据 role 的表面类型决定调用act(Role role)方法,属于静态绑定,而Actor的执行方法 act 则是由其实际类型决定的,这是动态绑定。

双分派

  • 得到的执行操作,决定于请求的种类和两个接收者的类型。对应于Router的类型和Visitor的类型。

三、使用场景

  • 一个对象结构包含很多类,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。

  • 或者想对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作"污染"这些对象的类。

  • 或者你想针对对象不同,操作不同,拦截一些逻辑。

  • 需要将数据结构与不常用的操作进行分离的时候

四、优点

  • 满足开闭原则,由于访问者模式没有对原对象进行修改,只是新增了外部统一操作

  • 符合单一职责原则,Router 的两个子类负责 数据的加载、传输, RouterVisitor 两个子类负责具体的数据配置

  • 扩展性,如果路由器有改动,或者新增路由器的一些配置改动,完全可以就在Visitor的两个子类的修改就可以了

五、缺点

  • 具体元素对访问者公布细节,即TPLinkRouter 类需要对WindowsRouterVisitor 的具体实现了解,需要了解它的方法和数据,违反了迪米特法则。

  • 具体元素变更比较困难,即路由器新增一个还好办,如果新增很多个呢?或者RouterVisitor类新增了属性呢?改动很大的。