我是荔园微风,作为一名在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深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。