2023-04-01  阅读(2)
原文作者:LoveLion 原文地址:https://blog.csdn.net/lovelion/article/details/7433523

26.3 完整解决方案

Sunny软件公司开发人员使用访问者模式对OA系统中员工数据汇总模块进行重构,使得系统可以很方便地增加新类型的访问者,更加符合“单一职责原则”和“开闭原则”,重构后的基本结构如图26-3所示:

202304012110077831.png

在图26-3中,FADepartment表示财务部,HRDepartment表示人力资源部,它们充当具体访问者角色,其抽象父类Department充当抽象访问者角色;EmployeeList充当对象结构,用于存储员工列表;FulltimeEmployee表示正式员工,ParttimeEmployee表示临时工,它们充当具体元素角色,其父接口Employee充当抽象元素角色。完整代码如下所示:

    import java.util.*;
    
    //员工类:抽象元素类
    interface Employee
    {
    	public void accept(Department handler); //接受一个抽象访问者访问
    }
    
    //全职员工类:具体元素类
    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); //调用访问者的访问方法
    	}
    }
    
    //兼职员工类:具体元素类
    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); //调用访问者的访问方法
    	}
    }
    
    //部门类:抽象访问者类
    abstract class Department
    {
        //声明一组重载的访问方法,用于访问不同类型的具体元素
    	public abstract void visit(FulltimeEmployee employee);
    	public abstract void visit(ParttimeEmployee employee);	
    }
    
    //财务部类:具体访问者类
    class FADepartment extends Department
    {
        //实现财务部对全职员工的访问
    	public void visit(FulltimeEmployee employee)
    	{
    		int workTime = employee.getWorkTime();
    		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 + "元。");		
    	}		
    }
    
    //人力资源部类:具体访问者类
    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 + "小时。");
    	}		
    }
    
    //员工列表类:对象结构
    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);
    		}
    	}
    }

为了提高系统的灵活性和可扩展性,我们将具体访问者类的类名存储在配置文件中,并通过工具类XMLUtil来读取配置文件并反射生成对象,XMLUtil类的代码如下所示:

    import javax.xml.parsers.*;
    import org.w3c.dom.*;
    import org.xml.sax.SAXException;
    import java.io.*;
    class XMLUtil
    {
        //该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
        public static Object getBean()
        {
    		try
    		{
    			//创建文档对象
    			DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
    			DocumentBuilder builder = dFactory.newDocumentBuilder();
    			Document doc;							
    			doc = builder.parse(new File("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;
           	}
        }
    }

配置文件config.xml中存储了具体访问者类的类名,代码如下所示:

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

编写如下客户端测试代码:

    class Client
    {
    	public static void main(String args[])
    	{
    		EmployeeList list = new EmployeeList();
    		Employee fte1,fte2,fte3,pte1,pte2;
    
    		fte1 = new FulltimeEmployee("张无忌",3200.00,45);
    		fte2 = new FulltimeEmployee("杨过",2000.00,40);
    		fte3 = new FulltimeEmployee("段誉",2400.00,38);
    		pte1 = new ParttimeEmployee("洪七公",80.00,20);
    		pte2 = new ParttimeEmployee("郭靖",60.00,18);
    
    		list.addEmployee(fte1);
    		list.addEmployee(fte2);
    		list.addEmployee(fte3);
    		list.addEmployee(pte1);
    		list.addEmployee(pte2);
    
    		Department dep;
    		dep = (Department)XMLUtil.getBean();
    		list.accept(dep);
    	}
    }

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

正式员工张无忌实际工资为:3700.0元。

正式员工杨过实际工资为:2000.0元。

正式员工段誉实际工资为:2240.0元。

临时工洪七公实际工资为:1600.0元。

临时工郭靖实际工资为:1080.0元。

如果需要更换具体访问者类,无须修改源代码,只需修改配置文件,例如将访问者类由财务部改为人力资源部,只需将存储在配置文件中的具体访问者类FADepartment改为HRDepartment,如下代码所示:

    <?xml version="1.0"?>
    <config>
        <className>HRDepartment</className>
    </config>

重新运行客户端程序,输出结果如下:

正式员工张无忌实际工作时间为:45小时。

正式员工张无忌加班时间为:5小时。

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

正式员工段誉实际工作时间为:38小时。

正式员工段誉请假时间为:2小时。

临时工洪七公实际工作时间为:20小时。

临时工郭靖实际工作时间为:18小时。

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

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

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


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文