一.需求
开发一个薪水支付系统,为公司每个雇员支付薪水,系统必须按照规定的方法准时地给雇员支付正确数目的薪水,同时,必须从雇员的薪水中减去各种扣款。
雇员的种类:
- 钟点工,按小时付费,当工作时间超过8小时时,按超过的部分按1.5倍收费。雇员信息中会给出每小时的酬金,每周五支付。
- 月薪雇员,在每个月的最后一天发放工资,雇员信息中会给出月薪的酬金。
- 带薪雇员,固定的工资+销售提成,提成根据销售情况来算,雇员会提交销售凭条,里面包含了销售日期和数量,雇员信息中含有固定工资字段和提成酬金字段,每隔一周的周五支付。
支付方式:
- 将支付支票邮寄到指定的邮政地址
- 将支付支票存到税务人员那里随时支取
- 将薪水直接打到银行账户中
扣款项:
- 雇员加入协会,协会每周会收取费用,另外协会会不定时的收取其他服务费用,费用直接从薪水中扣除。
运行方式:
可以输入指定日期或直接取当天日期,并发放该日期的薪水,可以运行一次或多次。
二.用例
三.初步分析
首先,雇员的种类有三种,第一时间可能会想到通过Employee来派生三个子类,但是,用例中有更改雇员类型的操作,钟点工和带薪雇员是可以转换的,这意味着,派生子类的设计不能在这里使用。我们可以使用策略模式,将雇员类型抽象为策略类,每个策略类中包含表示该类型雇员的特有字段,雇员的支付方式同理。最后是处理协会这个扣费项,采用组合的方式,使每个Employee对象包含一个协会对象Affiliation,当然这是个基类,因为雇员可以选择加入协会,也可以不选择,这需要两个子类来表示,一个是包含服务酬金的UnionAffiliation,表示加入协会,另一个是NoAffiliation,表示没有加入任何协会。
四.初步设计
五.设计背后的思考
在上述UML图中,我们并没有看到雇员支付薪水的方法,它的设计将被推迟到PaymentClassifiction类中,该类保存了计算薪水所需要的数据。另外,每种雇员他们支付薪水的时间点是完全不同的,但是UML图中并没有考虑到这点,我们很容易的想到使用策略模式,即将每个具体支付时间点抽象为策略类,具体设计如下。
六.具体实现
-
数据库对象
package cn.zzf.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author GaoFeng2017
* @date 2018-06-06 14:48:36
**/
public class EmpDB {
private Map employees = new HashMap<>(16);
private Map members = new HashMap<>(16);
private static EmpDB db = new EmpDB();
private EmpDB() {
}
public Employee getEmployee(Integer employeeId) {
return employees.get(employeeId);
}
public List getAll() {
return new ArrayList<>(employees.values());
}
public boolean removeEmployee(Integer employeeId) {
return employees.remove(employeeId) != null;
}
public void addEmployee(Employee employee) {
employees.put(employee.getEmployeeId(),employee);
}
public void clear() {
employees.clear();
}
public Employee getMember(Integer memberIds) {
return members.get(memberIds);
}
public void addUnionAffiliation(Integer memberId,Employee employee) {
members.put(memberId,employee);
}
public void removeUnionAffiliation(Integer memberId) {
members.remove(memberId);
}
public static EmpDB getDB() {
return db;
}
}
-
增加雇员
package cn.zzf.impl;
/**
* @author GaoFeng2017
* @date 2018-06-06 14:38:11
**/
public abstract class AddEmployeeTransaction implements Transaction {
private EmpDB db = EmpDB.getDB();
/** 雇员姓名 */
private String name;
/** 雇员住址 */
private String address;
/** 雇员Id */
private Integer EmpId;
public AddEmployeeTransaction(String name, String address, Integer empId) {
this.name = name;
this.address = address;
EmpId = empId;
}
@Override
public void execute() {
Employee e = new Employee(this.name,this.address,this.EmpId);
PaymentMethod method = new HoldMethod();
e.setPaymentClassification(getPaymentClassification());
e.setPaymentSchedule(getPaymentSchedule());
e.setPaymentMethod(new HoldMethod());
e.setAffiliation(new NoAffiliation());
db.addEmployee(e);
}
/**
* 获取雇员支付策略
* @author: GaoFeng2017
* @date: 2018/6/6 15:54
* @param:
* @return:
*
*/
public abstract PaymentClassification getPaymentClassification();
/**
* 获取支付时间表
* @author: GaoFeng2017
* @date: 2018/6/7 11:33
* @param:
* @return:
*
*/
public abstract PaymentSchedule getPaymentSchedule();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Integer getEmpId() {
return EmpId;
}
public void setEmpId(Integer empId) {
EmpId = empId;
}
}
package cn.zzf.impl;
/**
* @author GaoFeng2017
* @date 2018-06-06 16:05:17
**/
public class AddCommissionedTransaction extends AddEmployeeTransaction {
private double monthSalary;
private double commissionRate;
public AddCommissionedTransaction(String name, String address, Integer empId, double monthSalary, double commissionRate) {
super(name, address, empId);
this.monthSalary = monthSalary;
this.commissionRate = commissionRate;
}
@Override
public PaymentClassification getPaymentClassification() {
return new CommissionedClassification(monthSalary,commissionRate);
}
@Override
public PaymentSchedule getPaymentSchedule() {
return new BiWeeklySchedule();
}
}
AddEmployeeTransaction类使用了模板方法模式,每个addTransaction添加的步骤是一样的,只是雇员类型和支付薪水时间表不同而已,该操作被封装成了模板方法,子类只需实现getClassification,getSchedule两个抽象方法。这里只给出Commission雇员类型的实现,需要完整代码的可以在github上下载,下面的代码也将省略掉一些“重复“实现。
-
删除雇员
package cn.zzf.impl;
/**
* @author GaoFeng2017
* @date 2018-06-07 11:42:42
**/
public class DeleteEmployeeTransaction implements Transaction{
private Integer empId;
private EmpDB db = EmpDB.getDB();
public DeleteEmployeeTransaction(Integer empId) {
this.empId = empId;
}
@Override
public void execute() {
db.removeEmployee(empId);
}
}
-
时间卡,销售凭条以及服务费用
package cn.zzf.impl;
import java.util.Objects;
/**
* @author GaoFeng2017
* @date 2018-06-07 11:55:10
**/
public class TimeCardTransaction implements Transaction {
private Integer empId;
private TimeCard timeCard;
private EmpDB db = EmpDB.getDB();
public TimeCardTransaction() {
}
public TimeCardTransaction(Integer empId, TimeCard timeCard) {
this.empId = empId;
this.timeCard = timeCard;
}
@Override
public void execute() {
Employee employee = db.getEmployee(empId);
PaymentClassification paymentClassification = employee.getPaymentClassification();
if (!Objects.equals(paymentClassification.getClass(),HourlyClassification.class)) {
throw new RuntimeException("关联失败,雇员不是钟点工");
}
HourlyClassification classification = (HourlyClassification) employee.getPaymentClassification();
classification.getTimeCards().add(timeCard);
}
}
package cn.zzf.impl;
import java.util.Date;
/**
* @author GaoFeng2017
* @date 2018-06-07 10:37:51
**/
public class TimeCard {
private Date date;
private int hours;
public TimeCard() {
}
public TimeCard(Date date, int hours) {
this.date = date;
this.hours = hours;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public int getHours() {
return hours;
}
public void setHours(int hours) {
this.hours = hours;
}
}
package cn.zzf.impl;
import java.util.Date;
import java.util.LinkedList;
/**
* @author GaoFeng2017
* @date 2018-06-07 13:40:22
**/
public class ServiceChargeTransaction implements Transaction {
private Integer memberId;
private Date date;
private Double amount;
private EmpDB db = EmpDB.getDB();
public ServiceChargeTransaction() {
}
public ServiceChargeTransaction(Integer memberId, Date date, Double amount) {
this.memberId = memberId;
this.date = date;
this.amount = amount;
}
@Override
public void execute() {
Employee employee = db.getMember(memberId);
UnionAffiliation unionAffiliation = (UnionAffiliation) employee.getAffiliation();
unionAffiliation.getServiceCharges().add(new ServiceCharge(date,amount));
}
}
package cn.zzf.impl;
import java.util.Date;
/**
* @author GaoFeng2017
* @date 2018-06-07 11:16:15
**/
public class ServiceCharge {
private Date date;
private Double amount;
public ServiceCharge() {
}
public ServiceCharge(Date date, Double amount) {
this.date = date;
this.amount = amount;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Double getAmount() {
return amount;
}
public void setAmount(Double amount) {
this.amount = amount;
}
}
-
更改雇员属性
package cn.zzf.impl;
/**
* @author GaoFeng2017
* @date 2018-06-07 15:37:12
**/
public abstract class ChangeEmployeeTransaction implements Transaction{
private Integer empId;
private EmpDB db = EmpDB.getDB();
public ChangeEmployeeTransaction(Integer empId) {
this.empId = empId;
}
public ChangeEmployeeTransaction() {
}
@Override
public void execute() {
Employee e = db.getEmployee(empId);
if (e != null) {
change(e);
}
}
/** 改变雇员属性 */
public abstract void change(Employee employee);
}
package cn.zzf.impl;
/**
* @author GaoFeng2017
* @date 2018-06-07 15:44:39
**/
public class ChangeNameTransaction extends ChangeEmployeeTransaction {
private String name;
public ChangeNameTransaction(Integer empId, String name) {
super(empId);
this.name = name;
}
@Override
public void change(Employee employee) {
employee.setName(name);
}
}
package cn.zzf.impl;
/**
* @author GaoFeng2017
* @date 2018-06-07 15:49:35
*
**/
public abstract class ChangeClassificationTransaction extends ChangeEmployeeTransaction {
public ChangeClassificationTransaction(Integer empId) {
super(empId);
}
@Override
public void change(Employee employee) {
employee.setPaymentClassification(getPaymentClassification());
employee.setPaymentSchedule(getPaymentSchedule());
}
/**
* 获取雇员要更改的支付策略
* @author: GaoFeng2017
* @date: 2018/6/7 16:00
* @param:
* @return:
*
*/
public abstract PaymentClassification getPaymentClassification();
/**
* 获取雇员要更改的薪水支付时间表
* @author: GaoFeng2017
* @date: 2018/6/7 16:00
* @param:
* @return:
*
*/
public abstract PaymentSchedule getPaymentSchedule();
}
package cn.zzf.impl;
/**
* @author GaoFeng2017
* @date 2018-06-07 16:05:16
**/
public class ChangeHourlyTransaction extends ChangeClassificationTransaction {
private Double hourlyRate;
public ChangeHourlyTransaction(Integer empId, Double hourlyRate) {
super(empId);
this.hourlyRate = hourlyRate;
}
@Override
public PaymentClassification getPaymentClassification() {
return new HourlyClassification(hourlyRate);
}
@Override
public PaymentSchedule getPaymentSchedule() {
return new WeeklySchedule();
}
}
package cn.zzf.impl;
/**
* @author GaoFeng2017
* @date 2018-06-07 16:39:10
**/
public abstract class ChangeAffiliationTransaction extends ChangeEmployeeTransaction {
public ChangeAffiliationTransaction(Integer empId) {
super(empId);
}
@Override
public void change(Employee employee) {
recordMembership(employee);
employee.setAffiliation(getAffiliation());
}
/**
* 获取雇员要修改的会员
*
* @author: GaoFeng2017
* @date: 2018/6/7 16:42
* @param:
* @return:
*
*/
public abstract Affiliation getAffiliation();
/**
* 确定当前雇员关系
* @author GaoFeng2017
* @date 2018/6/7 18:11
* @param employee 雇员对象
* @return
*
*/
public abstract void recordMembership(Employee employee);
}
package cn.zzf.impl;
/**
* @author GaoFeng2017
* @date 2018-06-07 16:49:04
**/
public class ChangeMemberTransaction extends ChangeAffiliationTransaction {
private Double dus;
private Integer memberId;
private EmpDB db = EmpDB.getDB();
public ChangeMemberTransaction(Integer empId, Double dus, Integer memberId) {
super(empId);
this.dus = dus;
this.memberId = memberId;
}
@Override
public Affiliation getAffiliation() {
return new UnionAffiliation(memberId,dus);
}
@Override
public void recordMembership(Employee employee) {
db.addUnionAffiliation(memberId,employee);
}
}
package cn.zzf.impl;
import java.util.Objects;
/**
* @author GaoFeng2017
* @date 2018-06-07 16:52:27
**/
public class ChangeNoMemberTransaction extends ChangeAffiliationTransaction {
private EmpDB db = EmpDB.getDB();
public ChangeNoMemberTransaction(Integer empId) {
super(empId);
}
// private
@Override
public Affiliation getAffiliation() {
return new NoAffiliation();
}
@Override
public void recordMembership(Employee employee) {
Affiliation affiliation = employee.getAffiliation();
if (Objects.equals(affiliation.getClass(),NoAffiliation.class)) {
return;
}
UnionAffiliation unionAffiliation = (UnionAffiliation) affiliation;
db.removeUnionAffiliation(unionAffiliation.getMemberId());
}
}
package cn.zzf.impl;
import java.util.Date;
import java.util.LinkedList;
/**
* @author GaoFeng2017
* @date 2018-06-07 10:59:57
**/
public class UnionAffiliation implements Affiliation {
private Integer memberId;
private double dues;
private LinkedList serviceCharges = new LinkedList<>();
public UnionAffiliation() {
}
public UnionAffiliation(Integer memberId, double dues) {
this.memberId = memberId;
this.dues = dues;
}
public double getDues() {
return dues;
}
public void setDues(double dues) {
this.dues = dues;
}
public LinkedList getServiceCharges() {
return serviceCharges;
}
public Integer getMemberId() {
return memberId;
}
public void setMemberId(Integer memberId) {
this.memberId = memberId;
}
@Override
public Double calculateDeductions(PayCheck payCheck) {
Date aDate = payCheck.getPayStartTime();
Date bDate = payCheck.getPayDate();
int count = DateUtil.getFridayCount(aDate,bDate);
Double sum = dues * count;
for (ServiceCharge serviceCharge : serviceCharges) {
if (DateUtil.isBetweenDate(serviceCharge.getDate(),aDate,bDate)) {
sum += serviceCharge.getAmount();
}
}
return sum;
}
}
package cn.zzf.impl;
/**
* @author GaoFeng2017
* @date 2018-06-07 19:19:56
**/
public class NoAffiliation implements Affiliation {
@Override
public Double calculateDeductions(PayCheck payCheck) {
return 0.0;
}
}
值得注意的是,这里的ChangeAffiliationTransaction多了一个名为recordMembership的抽象方法,它将会检测雇员是否加入过协会,并且解除已经加入的协会关系。
七.支付薪水
package cn.zzf.impl;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
/**
* @author GaoFeng2017
* @date 2018-06-06 14:48:51
*
**/
public class Employee {
private String name;
private String address;
private Integer employeeId;
private PaymentClassification paymentClassification;
private PaymentMethod paymentMethod;
private PaymentSchedule paymentSchedule;
private Affiliation affiliation;
public Employee(String name, String address, Integer employeeId) {
this.name = name;
this.address = address;
this.employeeId = employeeId;
}
public Affiliation getAffiliation() {
return affiliation;
}
public void setAffiliation(Affiliation affiliation) {
this.affiliation = affiliation;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Integer getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public PaymentClassification getPaymentClassification() {
return paymentClassification;
}
public void setPaymentClassification(PaymentClassification paymentClassification) {
this.paymentClassification = paymentClassification;
}
public PaymentMethod getPaymentMethod() {
return paymentMethod;
}
public void setPaymentMethod(PaymentMethod paymentMethod) {
this.paymentMethod = paymentMethod;
}
public PaymentSchedule getPaymentSchedule() {
return paymentSchedule;
}
public void setPaymentSchedule(PaymentSchedule paymentSchedule) {
this.paymentSchedule = paymentSchedule;
}
public Date getStartPayDate(Date date) {
return paymentSchedule.getStartPayDate(date);
}
public void PayDay(PayCheck payCheck) {
Double grossPay = paymentClassification.calculatePay(payCheck);
Double deductions = affiliation.calculateDeductions(payCheck);
Double netPay = grossPay - deductions;
payCheck.setDeductions(deductions);
payCheck.setGrossPay(grossPay);
payCheck.setNetPay(netPay);
paymentMethod.pay(payCheck);
}
}
package cn.zzf.impl;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author GaoFeng2017
* @date 2018-06-07 20:31:48
**/
public class PaydayTransaction implements Transaction {
private Date date;
private EmpDB db = EmpDB.getDB();
public static Map map = new HashMap<>(16);
public PaydayTransaction(Date date) {
this.date = date;
}
@Override
public void execute() {
List employees = db.getAll();
for (Employee employee : employees) {
System.out.println(employee.getPaymentSchedule().isPayDay(date));
if (employee.getPaymentSchedule().isPayDay(date)) {
PayCheck payCheck = new PayCheck(employee.getStartPayDate(date),date);
employee.PayDay(payCheck);
map.put(employee.getEmployeeId(),payCheck);
}
}
}
}
测试程序写的比较混乱,就不贴上去了。
当execute被调用执行时,将会遍历每一个雇员对象,并且和给定的支付日期匹配,如果当前雇员的支付日期为给定日期,就发放薪水。不过,这里需要依靠payCheck对象来检测是否重复发放薪水。
完整代码 https://github.com/ZGAOF/oop-design/tree/master/salary-pay/src/main/java/cn/zzf/impl