薪水支付案例

一.需求

开发一个薪水支付系统,为公司每个雇员支付薪水,系统必须按照规定的方法准时地给雇员支付正确数目的薪水,同时,必须从雇员的薪水中减去各种扣款。

雇员的种类:

  • 钟点工,按小时付费,当工作时间超过8小时时,按超过的部分按1.5倍收费。雇员信息中会给出每小时的酬金,每周五支付。
  • 月薪雇员,在每个月的最后一天发放工资,雇员信息中会给出月薪的酬金。
  • 带薪雇员,固定的工资+销售提成,提成根据销售情况来算,雇员会提交销售凭条,里面包含了销售日期和数量,雇员信息中含有固定工资字段和提成酬金字段,每隔一周的周五支付。

支付方式:

  • 将支付支票邮寄到指定的邮政地址
  • 将支付支票存到税务人员那里随时支取
  • 将薪水直接打到银行账户中

扣款项:

  • 雇员加入协会,协会每周会收取费用,另外协会会不定时的收取其他服务费用,费用直接从薪水中扣除。

运行方式:

可以输入指定日期或直接取当天日期,并发放该日期的薪水,可以运行一次或多次。

二.用例

image.png
image.png
image.png

三.初步分析

首先,雇员的种类有三种,第一时间可能会想到通过Employee来派生三个子类,但是,用例中有更改雇员类型的操作,钟点工和带薪雇员是可以转换的,这意味着,派生子类的设计不能在这里使用。我们可以使用策略模式,将雇员类型抽象为策略类,每个策略类中包含表示该类型雇员的特有字段,雇员的支付方式同理。最后是处理协会这个扣费项,采用组合的方式,使每个Employee对象包含一个协会对象Affiliation,当然这是个基类,因为雇员可以选择加入协会,也可以不选择,这需要两个子类来表示,一个是包含服务酬金的UnionAffiliation,表示加入协会,另一个是NoAffiliation,表示没有加入任何协会。

四.初步设计

image.png

五.设计背后的思考

在上述UML图中,我们并没有看到雇员支付薪水的方法,它的设计将被推迟到PaymentClassifiction类中,该类保存了计算薪水所需要的数据。另外,每种雇员他们支付薪水的时间点是完全不同的,但是UML图中并没有考虑到这点,我们很容易的想到使用策略模式,即将每个具体支付时间点抽象为策略类,具体设计如下。

image.png

六.具体实现

  • 数据库对象

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;
    }
}

  • 增加雇员

image.png

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);
    }
}

  • 时间卡,销售凭条以及服务费用

image.png
image.png
image.png
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;
    }
}

  • 更改雇员属性

image.png
image.png
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

你可能感兴趣的:(薪水支付案例)