案例来源:《Agile Software Dvelopment Principles》(敏捷软件开发)一书

原始需求

用到的单词

Salary: 薪水
Employee: 员工
Sales Receipt : 销售凭条
Classification: 分类
Payment: 支付
Affiliation:从属关系

详细需求描述

该系统由一个公司数据库以及和员工相关的数据(例如工作时间卡)组成,
系统需要准时地按照规则给员工支付薪水,同时,必须从薪水中扣除各种扣款。

员工的类型和他们的薪水支付规则如下:

  • 钟点工
    • 会按照他们员工记录中每小时的报酬字段的值对他们进行支付,他们每天提交工作时间卡,其中记录了日期以及工作小时数,如果每天工作超过8小时,按1.5倍进行支付。 每周五对他们进行支付。
  • 固定薪资员工
    • 完全以月薪进行支付,每个月的最后一个工作日对他们进行支付,在他们的员工记录中有个月薪字段。
  • 销售员
    • 销售员除了固定薪资之外,会根据他们的销售情况,支付给他们一定数量的佣金,他们会提交销售凭条,其中记录了销售的日期和数量。在他们的雇员记录中有一个酬金报酬字段。 每隔一周的周五对他们进行支付。

员工可以选择支付方式,可以把支票邮寄到他们指定的邮政地址,也可以保存在财务那里随时支取,或者要求直接存入他们指定的银行账户。

一些员工会加入协会,在他们的员工记录中有一个每周应付款项字段,这些应付款需要从他们的薪水中扣除。 协会有时会针对单个会员征收服务费用。 协会每周会提交这些服务费用,服务费用从相应雇员的薪水总额中扣除。

薪水支付程序每个工作日运行一次, 并在当天对相应的雇员进行支付,系统会被告知雇员的支付日期,这样它会计算从雇员上次支付日期到规定的支付日期间应付的数额。

需求提炼

  1. 员工类型:
    • 小时工:每天提交工作时间卡,记录了日、工作小时数,如果每天工作超过8小时,按1.5倍进行支付, 每周五支付;
    • 固定薪资:每个月的最后一个工作日对他们进行支付,在员工中有个月薪字段;
    • 销售员:带薪的员工,按照提成比例给佣金。提交销售凭条,记录日期和金额。在员工中有一个提成比例字段。 每隔一周的周五支付;
  2. 支付方式:
    • 支票邮寄、保存在财务、银行转账;
  3. 扣除项:
    • 在雇员记录中有一个每周应付款项字段,这些应付款需要从他们的薪水中扣除。
  4. 运行周期:
    • 程序每个工作日运行一到多次, 对相应的员工进行支付,系统在支付前需要计算出每个员工的支付日期,这样就可以计算出从上次支付日期到支付日期间应付的薪资。

      设计过程

      员工的设计

      首先我们对三种员工进行设计:
      一种思路是将员工分为三种类型:
      未命名.png
      很明显我们需要一个Employee类把三种不同类型员工进行抽象,保存员工的姓名和地址,然后让三个子类继承它。
      Employee.png

      支付方式的设计

      接着我们很容易的设计出三种支付方式:
      支付方式 (1).png
      但是怎么把这三个支付方式和刚才设计的类Employee进行关联?
      毫无疑问,需要抽象,我们抽象一个PayMethod去和Employee进行关联。
      支付方式 (2).png
      然后我们将PayMethod和Employee关联在一起:
      Employee (1).png
      关系说明:
      SalesReceipt、TimeCard与SalesEmployee、HourEmployee为组合关系(同生共死);
      PayMethod类与Employee类的关系为聚合(局部可单独存在,即PayMethod离开Employee也可单独存在)

      隐藏的需求

      现在问题来了,假如公司的小时工转岗为销售了,按照销售类来支付,怎么办?

一种解决思路是,在Employee中增加employeeType字段来表示员工类型,
比如1表示小时工,2表示固定薪资员工,3表示销售员工,
那么将员工类型改掉就可以满足变化的需求了。但是如果这个员工是做了半个月固薪员工,半个月的销售员,那么他的薪资怎么算?
增加employeeType字段并解决不了问题,反而需要做很多的if进行判断员工类型,增大了代码的复杂度和产生bug的概率。

我们会发现在做抽象时,抽象的如果是不变的部分,那就搞错方向了,应该提取变化的部分做抽象。
这时候怎么办呢,先考虑我们的原始需求,我们的原始需求是薪水的支付,员工转岗之后,变化的部分是他的支付薪水的方式发生了变化,我们将员工的支付进行抽象为支付策略(策略模式-将不同的算法封装起来)。
支付类型.png

在哪里计算薪水?

PaymentClassification是个不错的地方, 因为它的子类对应于不同的员工类型,
对于一个给定的日期,如果这一天应该支付薪水,则进行薪水的计算。
计算薪水.png
这样设计有没有什么问题呢?好像不满足SRP原则,子类又判断是否是付薪日,又计算工资!要是一个类只做一件事情就好了。
那么抽象出一个支付计划来专门计算日期:
image.png
WeeklySchedule : 每周五支付
BiWeeklySchedule : 每隔一周支付
MonthlySchedule : 每月的最后一天支付
isPayDay:判断是是不是支付日
getPayPeriodStartDate:获取支付时间段开始的日期

细节处理

重复发薪的错误处理:
薪水支付程序每天都会运行, 甚至一天运行多次,会不会产生多次发薪的错误?
需要一个PayCheck类来进行检查,把成功运行的支付记录保存下来,主要的职责是跟着Employee对象,在计算薪水、扣除项时全部在场。
image.png
计算薪水的处理:
计算薪水 (1).png
漏了的需求:
一些员工会加入协会,在他们的雇员记录中有一个每周应付款项字段,这些应付款需要从他们的薪水中扣除。
协会有时会针对单个会员征收服务费用。 协会每周会提交这些服务费用,服务费用从相应雇员薪水总额中扣除。
image.png
由于是扣除项针对某些员工收取的,那有的员工没有加入协会我们怎么处理?
我们可以使用空对象模式(Null Object Pattern),让他们不做任何操作就好了!
image.png