26.3 完整解決方案
Sunny軟體公司開發人員使用通路者模式對OA系統中員工資料彙總子產品進行重構,使得系統可以很友善地增加新類型的通路者,更加符合“單一職責原則”和“開閉原則”,重構後的基本結構如圖26-3所示:
在圖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小時。 |
如果要在系統中增加一種新的通路者,無須修改源代碼,隻要增加一個新的具體通路者類即可,在該具體通路者中封裝了新的操作元素對象的方法。從增加新的通路者的角度來看,通路者模式符合“開閉原則”。
如果要在系統中增加一種新的具體元素,例如增加一種新的員工類型為“退休人員”,由于原有系統并未提供相應的通路接口(在抽象通路者中沒有聲明任何通路“退休人員”的方法),是以必須對原有系統進行修改,在原有的抽象通路者類和具體通路者類中增加相應的通路方法。從增加新的元素的角度來看,通路者模式違背了“開閉原則”。
綜上所述,通路者模式與抽象工廠模式類似,對“開閉原則”的支援具有傾斜性,可以很友善地添加新的通路者,但是添加新的元素較為麻煩。
【作者:劉偉 http://blog.csdn.net/lovelion】