一.需求
開發(fā)一個薪水支付系統(tǒng)牲尺,為公司每個雇員支付薪水宪卿,系統(tǒng)必須按照規(guī)定的方法準時地給雇員支付正確數(shù)目的薪水,同時望众,必須從雇員的薪水中減去各種扣款匪补。
雇員的種類:
- 鐘點工,按小時付費烂翰,當工作時間超過8小時時夯缺,按超過的部分按1.5倍收費。雇員信息中會給出每小時的酬金甘耿,每周五支付踊兜。
- 月薪雇員,在每個月的最后一天發(fā)放工資佳恬,雇員信息中會給出月薪的酬金捏境。
- 帶薪雇員,固定的工資+銷售提成毁葱,提成根據(jù)銷售情況來算垫言,雇員會提交銷售憑條,里面包含了銷售日期和數(shù)量倾剿,雇員信息中含有固定工資字段和提成酬金字段筷频,每隔一周的周五支付。
支付方式:
- 將支付支票郵寄到指定的郵政地址
- 將支付支票存到稅務人員那里隨時支取
- 將薪水直接打到銀行賬戶中
扣款項:
- 雇員加入?yún)f(xié)會,協(xié)會每周會收取費用凛捏,另外協(xié)會會不定時的收取其他服務費用担忧,費用直接從薪水中扣除。
運行方式:
可以輸入指定日期或直接取當天日期葵袭,并發(fā)放該日期的薪水涵妥,可以運行一次或多次。
二.用例
三.初步分析
首先坡锡,雇員的種類有三種蓬网,第一時間可能會想到通過Employee來派生三個子類,但是鹉勒,用例中有更改雇員類型的操作帆锋,鐘點工和帶薪雇員是可以轉(zhuǎn)換的,這意味著禽额,派生子類的設計不能在這里使用锯厢。我們可以使用策略模式,將雇員類型抽象為策略類脯倒,每個策略類中包含表示該類型雇員的特有字段实辑,雇員的支付方式同理。最后是處理協(xié)會這個扣費項藻丢,采用組合的方式剪撬,使每個Employee對象包含一個協(xié)會對象Affiliation,當然這是個基類悠反,因為雇員可以選擇加入?yún)f(xié)會残黑,也可以不選擇,這需要兩個子類來表示斋否,一個是包含服務酬金的UnionAffiliation梨水,表示加入?yún)f(xié)會,另一個是NoAffiliation茵臭,表示沒有加入任何協(xié)會疫诽。
四.初步設計
五.設計背后的思考
在上述UML圖中,我們并沒有看到雇員支付薪水的方法旦委,它的設計將被推遲到PaymentClassifiction類中奇徒,該類保存了計算薪水所需要的數(shù)據(jù)。另外社证,每種雇員他們支付薪水的時間點是完全不同的,但是UML圖中并沒有考慮到這點评凝,我們很容易的想到使用策略模式追葡,即將每個具體支付時間點抽象為策略類,具體設計如下。
六.具體實現(xiàn)
-
數(shù)據(jù)庫對象
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<Integer,Employee> employees = new HashMap<>(16);
private Map<Integer,Employee> members = new HashMap<>(16);
private static EmpDB db = new EmpDB();
private EmpDB() {
}
public Employee getEmployee(Integer employeeId) {
return employees.get(employeeId);
}
public List<Employee> 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添加的步驟是一樣的匀钧,只是雇員類型和支付薪水時間表不同而已,該操作被封裝成了模板方法谬返,子類只需實現(xiàn)getClassification之斯,getSchedule兩個抽象方法。這里只給出Commission雇員類型的實現(xiàn)遣铝,需要完整代碼的可以在github上下載佑刷,下面的代碼也將省略掉一些“重復“實現(xiàn)。
-
刪除雇員
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("關聯(lián)失敗瘫絮,雇員不是鐘點工");
}
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<ServiceCharge> 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<ServiceCharge> 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的抽象方法填硕,它將會檢測雇員是否加入過協(xié)會麦萤,并且解除已經(jīng)加入的協(xié)會關系。
七.支付薪水
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<Integer,PayCheck> map = new HashMap<>(16);
public PaydayTransaction(Date date) {
this.date = date;
}
@Override
public void execute() {
List<Employee> 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);
}
}
}
}
測試程序?qū)懙谋容^混亂扁眯,就不貼上去了壮莹。
當execute被調(diào)用執(zhí)行時,將會遍歷每一個雇員對象姻檀,并且和給定的支付日期匹配命满,如果當前雇員的支付日期為給定日期,就發(fā)放薪水施敢。不過周荐,這里需要依靠payCheck對象來檢測是否重復發(fā)放薪水。
完整代碼 https://github.com/ZGAOF/oop-design/tree/master/salary-pay/src/main/java/cn/zzf/impl