访问者模式

前言

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

行为型模式还能够分为如下两种。算法

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

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

目录

1、定义

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

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

2、模式原理分析

先上模板代码,再举例ide

//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)作路由器发送数据的兼容功能this

//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,整个过程完成了两次动态绑定。spa

单分派操作系统

  • 单分派语言处理一个操做是根据请求者的名称和接受到的参数决定的,在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的类型。

3、使用场景

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

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

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

  • 须要将数据结构与不经常使用的操做进行分离的时候

4、优势

  • 知足开闭原则,因为访问者模式没有对原对象进行修改,只是新增了外部统一操做

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

  • 扩展性,若是路由器有改动,或者新增路由器的一些配置改动,彻底能够就在Visitor的两个子类的修改就能够了

5、缺点

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

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

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息