Windows环境下实现设计模式——访问者模式(JAVA版)

news2025/1/23 4:10:37

我是荔园微风,作为一名在IT界整整25年的老兵,今天总结一下Windows环境下如何编程实现访问者模式(设计模式)。

不知道大家有没有这样的感觉,看了一大堆编程和设计模式的书,却还是很难理解设计模式,无从下手。为什么?因为你看的都是理论书籍。

我今天就以JAVA编程语言来实现一个访问者模式,真实的实现一个,你看懂代码后,自然就明白了。

访问者模式Visitor Pattern(行为型设计模式)

定义:表示一个作用于某对象结构中的各个元素的操作。访问者模式让用户可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

上面定义听懂了吗?莫名其妙看不懂对吧。所以我们还是来看看实现生活中的例子。

相信大家在实现手机交电费以前,总是和一个叫电费单的东西打交道。如下:

在电力公司开具电费单后,电力公司财务人员拿到单之后根据用电情况计算总价,片区管理工作人员根据上面内容通知用户收取电费,而用户则要根据电费单准备好钱进行交费。

可以将电费单看成一个信息的集合,里面包含了一种或多种不同类型的信息,不同类型的人员在操作同一个信息集合时将提供不同的处理方式,而且可能还会增加新类型的人员来操作电费单。

在软件开发中有时候也需要处理像电费单这样的集合对象结构,在该对象结构中存储了多种不同类型的对象信息,而且对同一对象结构中的元素的操作方式并不唯一,可能需要提供多种不同的处理方式,还有可能增加新的处理方式。在设计模式中有一种模式可以满足这个要求,该模式就是访问者模式,就是以不同的方式操作复杂对象结构。

访问者模式包含访问者和被访问元素两个主要组成部分,这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作。例如电费单中的各种信息就是被访问的元素,而各类人员就是访问者。访问者模式使得用户可以在不修改现有系统的情况下扩展系统的功能,为这些不同类型的元素增加新的操作。

在使用访问者模式时,被访问元素通常不是单独存在的,它们存储在一个集合中,这个集合被称为“对象结构”,访问者通过遍历对象结构实现对其中存储的元素的挨个操作。其结构如下图:

访问者模式包含以下5个角色。

(1)visitor(抽象访问者):抽象访问者为对象结构中的每一个具体元素类声明一个问操作,从这个操作的名称或参数类型可以清楚地知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作。

(2)ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。

(3)Element(抽象元素):抽象元素一般是抽象类或者接口,它声明了一个accept()方法,用于接受访问者的访问操作,该方法通常以一个抽象访问者作为参数。

(4)ConcreteElement(具体元素);具体元素实现了 accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。

(5)ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象并且提供了遍历其内部元素的方法。对象结构可以结合组合模式来实现,也可以是一个简单的集合对象。

在访问者模式中,对象结构存储了不同类型的元素对象,以供不同访问者访问。访问者模式包括两个层次结构:一个是访问者层次结构,提供了抽象访问者和具体访问者;一个是元素层次结构,提供了抽象元素和具体元素。相同的访问者可以用不同的方式访问不同的元素.相同的元素可以接受不同访问者以不同的方式访问。在访问者模式中增加新的访问者无须修改原有系统,系统具有较好的可扩展性。

JAVA代码实现

public abstract class Visitor {
  public abstract void visit(ConcreteElementA elementA);
  public abstract void visit(ConcreteElementB elementB);
  
   public void visit(ConcreteElementC elementC){
  //元素ConcreteElementC操作代码
   }
}

public class Concretevisitor extends Visitor {
  public void visit(ConcreteElementA elementA){
  //元素ConcreteElementA操作代码
  }
  public void visit(ConcreteElementB elementB){
  //元素ConcreteElementB操作代码
  }
}

public interface Element{
  public void accept(Visitor visitor);
}

public class ConcreteElementA implements Element {
   public void accept(Visitor visitor){
       visitor.visit(this);
   }
  public void operationA()(
   //业务方法
   }
}


public class ObjectStructure{
    //定义一个集合用于存储元素对象  
   private ArrayList <Element> list new ArrayList <Element>();
   
  //接受访问者的访问操作
  public void accept(Visitor visitor){
      Iterator i=list. iterator();
  
       while(i.hasNext()){
          //遍历访问集合中的每一个元素
          ((Element)i.next()).accept(visitor);
        }
    }

    public void addElement(Element element){
          list.add(element) ; 
    }

    public void remove Element(Element element) { 
          list.remove(element) ;
    }
}

应用实例

某电力公司云平台中包含一个员工数据系统,员工包括正式员工和非正式员工,每周公司要对员工工作时间、员工工资等数据进行处理。公司有两个部门要对工作人员的情况进行统计,分别是人事部和财务部。人事部负责汇总每周员工工作时间,而财务部负责计算每周员工工资。公司制度如下:

(1)正式员工每周工作时间为40小时,不同级别、不同部门的员工每周基本工资不同;如果超过40小时,超出部分按照100元/小时作为加班费;如果少于40小时,所缺时间按照请假处理,请假所扣工资以80元/小时计算,直到基本工资扣除到零为止。

(2)非正式员工每周工作时间不固定,基本工资按小时计算,不同岗位的临时工小时工资不同。公司只需记录实际工作时间。

我们约定:

HRDepartment表示人事部,FADepartment表示财务部,这是访问者角色, 其抽象父类Department充当抽象访问者角色。

EmployeeList充当对象结构,用于存储员工列表; FullTimeEmployee表示正式员工, PartTimeEmployee表示非正式员工, 这是具体元素角色, 其父接口Employee充当抽象元素角色。

(1)Employee:员工类,充当抽象元素类。

package designpatterns.visitor;

public interface Employee {
  public void accept(Department handler); //接受一个抽象访问者访问
}

(2)FullTimeEmployee:全职员工类,充当具体元素类。

package designpatterns.visitor;
 
public class FullTimeEmployee implements Employee {
   private String name;     //员工姓名
   private double weeklyWage;    //员工周薪
   private int workTime;    //工作时间

   public FullTimeEmployee(String name, double weeklyWage, int workTime){
      this. name= name;
     this. weeklyWage= weeklyWage;
     this. workTime= workTime;
   }

  public void setName(String name){
     this. name= name;
   }

  public void setWeeklyWage(double weeklyWage){
      this. weeklyWage= weeklyWage;
   }

  public void setWorkTime(int workTime){
     this. workTime= workTime;
   }

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

  public double getWeeklyWage() {
     return (this. weeklyWage);
   }

  public int getWorkTime(){
     return(this. workTime);
   }

  public void accept(Department handler)(
     handler.visit(this);    //调用访问者的访问方法
  }
}

(3)PartTimeEmployee:兼职员工类,充当具体元素类。

package designpatterns.visitor;
 
public class PartTimeEmployee implements Employee {
   private String name;     //员工姓名
   private double hourWage;    //员工时薪
   private int workTime;    //工作时间

   public PartTimeEmployee(String name, double hourWage, int workTime){
      this. name= name;
     this. hourWage= hourWage;
     this. workTime= workTime;
   }

  public void setName(String name){
     this. name= name;
   }

  public void setHourWage(double HourWage){
      this. HourWage= HourWage;
   }

  public void setWorkTime(int workTime){
     this. workTime= workTime;
   }

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

  public double getHourWage() {
     return (this. HourWage);
   }

  public int getWorkTime(){
     return(this. workTime);
   }

  public void accept(Department handler)(
     handler.visit(this);    //调用访问者的访问方法
  }
}

 (4)Department:部门类,充当抽象访问者类。

package designpatterns.visitor;

public abstract class Department{
  //声明一组重载的访问方法,用于访问不同类型的具体元素
   public abstract void visit(FullTimeEmployee employee);
  public abstract void visit(PartTimeEmployee employee);
}

(5)FADepartment:财务部类,充当具体访问者类。

package designpatterns.visitor;

public class FADepartment extends Department {

    //实现财务部对全职员工的访问
  public void visit(FullTimeEmployee employee){
     int workTime= employee.getHorkTime();
     double weekWage = employee.getWeeklyWage();
     if(workTime >40){
           weekWage= weekWage+(workTime-40)*100;
      }
     else if(workTime<40){
         weekWage= weekWage-(40- workTime)*80;
         if(weekWage<0){
             weekWage=0;
          }
       }
       System.out.println("正式员工"+employee.getName()+"实际工资为:"+weekWage+"元");
   }

  //实现财务部对兼职员工的访问
  public void visit(PartTimeEmployee employee){
     int workTime= employee.getWorkTime();
     double hourWage= employee.getHourWage();
     System.out.println("非正式员工"+employee.getName()+"实际工资为:"+workTime*hourWage+"元");
   }
}

(6)HRDepartment:人力资源部类,充当具体访问者类。

package designpatterns.visitor;

public class HRDepartment extends Department{
  
   //实现人事部对全职员工的访问
  public void visit(FullTimeEmployee employee){
     int workTime= employee.getWorkTime();
     System.out.println("正式员工"+employee.getName()+"实际工作时间为:"+workTime+"小时");
     if(workTime >40){
          System.out.println("正式员工"+employee.getName()+"加班时间为:"+(workTime-40)+"小时");
       }
     else if(workTime<40){
           System.out.println("正式员工"+employee.getName()+"请假时间为:"+(40-workTime)+"小时");
       }
    }

  //实现人力资源部对兼职员工的访问

  public void visit(PartTimeEmployee employee){
     int workTime = employee.getWorkTime();
     System.out.println("非正式员工"+employee.getName()+"实际工作时间为:"+workTime+"小时");
  }
}

(7)EmployeeList:员工列表类,充当对象结构。

package designpatterns.visitor;
import java.util.#;

public class EmployeeList {
  //定义一个集合用于存储员工对象
  private ArrayList <Employee> list= new ArrayList <Employee>();
    
    public void addEmployee(Employee employee){
       list.add(employee);
  }

  //遍历访问员工集合中的每一个员工对象
  public void accept(Department handler){
      for(Object obj: list){
           ((Employee)obj).accept(handler);
        }
   }
}

(8)配置文件config.xml,在配置文件中存储了具体访问者类的类名。

<?xml version= "1.0"?>
<config>
  <className> designpatterns.visitor.FADepartment </className >
</config>

(9)XMLUtil:工具类。

package designpatterns.visitor;

import javax.xml.parsers.*;
import org.w3c.dom.*
import java.io.*;

public class XMLUtil {
  /该方法用于从XML配置文件中提取具体类的类名,并返回一个实例对象
   public static Object getBean(){
     try {
     //创建DOM文档对象
     DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
     DocumentBuilder builder= dFactory.newDocumentBuilder();
     Document doc;
     doc= builder.parse(new File("src//designpatterns//visitor//config,xml"));
    
     //获取包含类名的文本结点
     NodeList nl =doc. getElementsByTagName("className");
     Node classNode= nl. item(0). getFirstChild();
     String cName=classNode. getNodeValue();
   
      //通过类名生成实例对象并将其返回
     Class c=Class. forName(cName);
     Object obj=c. newInstance();
     return obj;
      }
      catch(Exception e){
          e. printStackTrace();
          return null;
      }
  }
}

(10)Client:客户端测试类。

package designpatterns. visitor;

public class Client{
  public static void main(String args[])(
     EmployeeList list= new EmployeeList();
     Employee fte1, fte2, fte3, ptel, pte2;
     ftel= new FullTimeEmployee("员工1",3000.00,50);
     fte2= new FullTimeEmployee("员工2",3000.00,40);
     fte3= new FullTimeErployee("员工3",3000.00,35);
     ptel= new PartTimeEmployee("员工4",80.00,25);
     pte2= new PartTimeEmployee("员工5",80.00,22);
 
      list.addEmployee(ftel);
     list.addEmployee(fte2);
     list,addEmployee(fte3);
     list.addEmployee(pte1);
     list.addEmployee(pte2);

     Department dep;
      dep=(Department)XMLUtil. getBean();
     list.accept(dep);
     }
}

编译并运行程序,输出结果如下:

正式员工员工1实际工资为:*****元。

正式员工员工2实际工资为:*****元。

正式员工员工3实际工资为:*****元。

非正式员工4实际工资为:*****元。

非正式员工5实际工资为:*****元。

如果需要更换具体访问者类,无须修改源代码,只需修改配置文件即可。将存储在配置文件config.xml中的具体访问者类FADepartment改为 HRDepartment。

重新编译并运行程序,输出结果如下:

正式员工员工1实际工作时间为:50小时。

正式员工员工1加班时间为:10小时。

正式员工员工2实际工作时间为:40小时。

正式员工员工3实际工作时间为:35小时。

正式员工员工3请假时间为:5小时。

非琥式员工员工4实际工作时间为:25小时。

非正式员工员工5实际工作时间为:22小时。

总结

如果要在系统中增加一种新的访问者,无须修改源代码,只要增加一个新的具体访问者类即可,在该具体访问者中封装了新的操作元素对象的方法。从增加新的访问者的角度来看,访问者模式符合开闭原则。

如果要在系统中增加一种新的具体元素,例如增加一种新的员工类型为“临时技术支持”,由于原有系统并未提供相应的访问接口(在抽象访问者中没有声明任何访问“临时技术支持”的方法),因此必须对原有系统进行修改,在原有的抽象访问者类和具体访问者类中增加相应的访问方法。从增加新的元素的角度来看,访问者模式违背了开闭原则。

综上所述,访问者模式对开闭原则的支持具有倾斜性,可以很方便地添加新的访问者,但是添加新的元素较为麻烦。

各位小伙伴,这次我们就说到这里,下次我们再深入研究windows环境下的设计模式,相信你一定能喜欢上windows。如果要转载我的文章请说明出处哦。

作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/397167.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】类和对象(收尾)

文章目录成员变量初始化问题初始化列表explicit关键字static成员特性&#xff1a;友元友元函数友元类内部类特性匿名对象成员变量初始化问题 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给了对象中各个成员变量一个合适的初始值。但是这并不能够称为对对象中成…

简单了解蓄电池在直流系统中的使用现状!

一般情况下&#xff0c;由市电通过直流配电屏为变电站的直流系统提供工作电源&#xff0c;包括对蓄电池组进行饱和和充电使蓄电池处于备用状态&#xff0c;当交流失电或系统需要进行大电流供电时&#xff0c;蓄电池需要迅速切入&#xff0c;向事故负荷、自动装置、保护装置以及…

本地套接字

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 本地套接字专栏&#xff1a;《Linux从小白到大神》《网络编程》 本地套接字通信需要一个文件&#xff…

tensorflow【import transformers 报错】

目录 一、安装 安装好了tensorflow,但是import时候报错&#xff1a; import transformers 报错 一、安装 &#xff08;1&#xff09;创建环境&#xff1a; conda create -n [name] python3.3-3.7 &#xff08;2&#xff09;激活环境&#xff1a; conda activate [name] …

Python中赋值、引用、深浅拷贝的区别和联系

文章目录一、对象的唯一id二、赋值三、可变对象和不可变对象四、函数的参数传递五、深拷贝和浅拷贝六、举个栗子6.1 不可变对象的拷贝6.2 可变对象的拷贝6.3 可变对象改变外层元素6.4 可变对象改变内层元素七、总结一、对象的唯一id python中的所有对象都有自己的唯一id&#…

典型回溯题目 - 全排列(一、二)

典型回溯题目 - 全排列&#xff08;一、二&#xff09; 46. 全排列 题目链接&#xff1a;46. 全排列状 题目大意&#xff1a; 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 注意&#xff1a;&#xff08;1&#xf…

Linux命令·which·whereis·locate·find

我们经常在linux要查找某个文件&#xff0c;但不知道放在哪里了&#xff0c;可以使用下面的一些命令来搜索&#xff1a; which 查看可执行文件的位置。whereis 查看文件的位置。 locate 配合数据库查看文件位置。find 实际搜寻硬盘查询文件名称。whichwhich命令的作用是&#x…

DJ1-1 操作系统引论

目录 一、操作系统的概念 二、操作系统的目标 三、操作系统的作用 一、操作系统的概念 定义一 操作系统是一组控制和管理计算机软硬件资源、合理地对各类作业进行调度以及方便用户使用的程序集合。 定义二 操作系统是位于硬件层&#xff08;HAL&#xff09;之上&#xff…

SQL 基础函数,通配符,BETWEEN ,用法复习

使用 SQL _ 通配符 下面的 SQL 语句选取 name 以一个任意字符开始&#xff0c;然后是 “oogle” 的所有客户&#xff1a; SELECT * FROM Websites WHERE name LIKE _oogle;下面的 SQL 语句选取 name 以 “G” 开始&#xff0c;然后是一个任意字符&#xff0c;然后是 “o”&am…

看完这篇我不信你不会二叉树的层序遍历【C语言】

目录 实现思路 代码实现 之前介绍了二叉树的前、中、后序三种遍历&#xff0c;采用的是递归的方式。今天我们来学习另外一种遍历方式——层序遍历。层序遍历不容小觑&#xff0c;虽然实现方法并不难&#xff0c;但是它所采取的思路是很值得学习的&#xff0c;与前三者不同&am…

学习笔记-架构的演进之容器的封装-3月day06

文章目录前言封装应用的Dockerwhy Docker not LXC?附前言 当文件系统、访问、资源都可以被隔离后&#xff0c;容器就已经具备它降生所需要的全部前置支撑条件了。为了降低普通用户综合使用 namespaces、cgroups 这些低级特性的门槛&#xff0c;2008 年 Linux Kernel 2.6.24 内…

Java中的final和权限修饰符

目录 final 常量 细节&#xff1a; 权限修饰符 Java权限修饰符用于控制类、方法、变量的访问范围。Java中有四种权限修饰符&#xff1a; 权限修饰符的使用场景&#xff1a; final 方法 表明该方法是最终方法&#xff0c;不能被重写。类 表明该类是最终类&#xff0c;不能被继…

Jetpack太香了,让开发效率提升了不少

作者&#xff1a;Jingle_zhang 第三方App使用Jetpack等开源框架非常流行&#xff0c;在Gradle文件简单指定即可。然而ROM内置的系统App在源码环境下进行开发&#xff0c;与第三方App脱节严重&#xff0c;采用开源框架的情况并不常见。但如果系统App也集成了Jetpack或第三方框架…

【UE4 RTS游戏】04-摄像机运动_鼠标移动到视口边缘时移动Pawn

效果可以看到当鼠标移动到视口边缘时&#xff0c;Pawn就会向这个方向移动。步骤打开项目设置&#xff0c;添加两个操作映射打开“CameraPawnController”&#xff0c;在事件图表中添加两个浮点型变量&#xff0c;一个为公有一个为私有。分别命名为“ZoomSensitivity”、“MaxAr…

【Linux】帮助文档查看方法

目录1 Linux帮助文档查看方法1.1 man1.2 内建命令(help)1 Linux帮助文档查看方法 1.1 man man 是 Linux 提供的一个手册&#xff0c;包含了绝大部分的命令、函数使用说明。 该手册分成很多章节&#xff08;section&#xff09;&#xff0c;使用 man 时可以指定不同的章节来浏…

ubuntu 系统安装docker——使用docker打包python项目,整个流程介绍

目录 1 安装docker和配置镜像源 2 下载基础镜像 3 通过镜像创建容器 4 制作项目所需的容器 5 容器制作好后打包为镜像 6 镜像备份为.tar文件 7 从其他服务器上恢复镜像 8 docker的其他常用指令 首先科普一下镜像、容器和实例&#xff1b; 镜像&#xff1a;相当于安装包&…

怎么用消息队列实现分布式事务?

当消息队列和事务联系在一起时&#xff0c;它指的是消息生产者和消息消费者之间如何保持数据一致性。 什么是分布式事务&#xff1f; 事务是指当我们进行若干项数据更新操作时&#xff0c;为了保证数据的完整性和一致性&#xff0c;我们希望这些更新操作要么都成功&#xff0…

蓝桥杯三月刷题 第五天

文章目录&#x1f4a5;前言&#x1f609;解题报告&#x1f4a5;数的分解&#x1f914;一、思路:&#x1f60e;二、代码&#xff1a;&#x1f4a5;前言 上午没写&#xff0c;下午写了会被朋友拉出去耍&#xff0c;被冷风吹到了&#xff0c;而且被他坑了&#xff0c;根本没有玩骑…

【源码库】在调用 createApp 时,Vue 为我们做了那些工作?

在使用Vue3时&#xff0c;我们需要使用createApp来创建一个应用实例&#xff0c;然后使用mount方法将应用挂载到某个DOM节点上。 那么在调用createApp时&#xff0c;Vue再背后做了些什么事情呢&#xff1f;今天就来扒一扒Vue3的源码&#xff0c;看看调用createApp发生了些什么…

八股文系列:Java虚拟机(JVM)

说一下 JVM 的主要组成部分及其作用&#xff1f; JVM包含两个子系统和两个组件&#xff0c;两个子系统为Class loader(类装载)、 Execution engine(执行引擎)&#xff1b;两个组件为Runtime data area(运行时数据 区)、Native Interface(本地接口)。 Class loader(类装载)&…