定义

DataClass是指:它们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。 这样的类只是一种不会说话的数据容器。

影响

  • DataClass将数据与行为间的关系割裂,破坏了面向对象的精髓。特定数据的操作分散在代码各处,可能造成代码霰弹式修改

改进目标

  • 将特定数据的操作集中在一个地方,提高代码的可读性、可维护性、可扩展性等

方法

  • 封装变量
  • 封装集合
  • 搬移函数
  • 抽取方法
  • 移除设值函数

案例

代码背景 部门及员工信息管理

  • Staff类为员工原始信息,属于数据模型, 存储于数据库中
    • 部长minister、部门名称name为部门Department固有属性,在业务代码中一旦设置后不会更改 员工staffs在部门department运作过程中可能会有新增和删除的情况
    • Audit类中的auditGenderRatio(Department department)为员工男女比例审计方法
    • Hrbp类中的getStaffNumOfGender(Gender gender)为获取指定性别的员工数量

症状/问题

  • 在Hrbp和Audit类中,都存在调用Department的staffs, 统计部门里男女员工数量的代码片段,属于功能重复
  • 人数统计功能,用的都是Department内部属性,应该放在Department中,功能更内聚

重构目标/步骤

  • 修改public属性访问修饰符为private
  • 不需要设值的属性删除set方法,提供构造方法
  • 集合属性删除设值函数,并提供add/delete方法
  • 集合属性的get,返回不可变集合,避免外部修改
/**
 * 部门信息
 */
public class Department {
    public String minister; // 部长

    private String name; // 部门名称

    private List<Staff> staffs; // 部门员工列表

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Staff> getStaffs() {
        return staffs;
    }

    public void setStaffs(List<Staff> staffs) {
        this.staffs = staffs;
    }
}
/**
 * 审计部门审计
 *
 * 男女比例与Hrbp里面计算存在重复代码,如果统计规则变化则需要同时修改两处
 *
 * demo中代码中没有考虑空指针相关等异常,实际项目请充分考虑异常场景处理
 */
public class Audit {
    /**
     * 审计男女比率
     *
     * @param department 部门信息
     * @return 男女比例 (没有女员工返回-1.0)
     */
    public double auditGenderRatio(Department department) {
        long maleNums = department.getStaffs().stream()
            .filter(staff -> Gender.MALE.equals(staff.getGender()))
            .count();
        long femaleNums = department.getStaffs().stream()
            .filter(staff ->  Gender.FEMALE.equals(staff.getGender()))
            .count();

        // 女员工数为0时 返回-1
        if (femaleNums == 0) {
            return -1.0;
        }
        return maleNums / (femaleNums * 1.0);
    }
}
/**
 * hrbp管理
 */
public class Hrbp {
    private final List<Department> departments; // 部门列表

    public Hrbp(List<Department> departments) {
        this.departments = departments;
    }

    /**
     * 统计所有部门下(男/女)性别员工数量
     *
     * @return (男/女)性别员工总数
     */
    public long getStaffNumOfGender(Gender gender) {
        long sum = 0L;
        for (Department department : departments) {
            long genderStaffNums = department.getStaffs().stream()
                .filter(staff -> staff.getGender().equals(gender))
                .count();
            sum += genderStaffNums;
        }
        return sum;
    }
}

改进手法:抽取方法

/**
 * 部门信息
 */
public class Department {
    private final String minister; // 部长

    private final String name; // 部门名称

    private List<Staff> staffs = new ArrayList<>(); // 部门员工列表

    public Department(String minister, String name) {
        this.minister = minister;
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public List<Staff> getStaffs() {
        return Collections.unmodifiableList(staffs);
    }

    public void addStaffs(List<Staff> staffs) {
        this.staffs.addAll(staffs);
    }

    public void deleteStaffs(List<Staff> staffs) {
        this.staffs.removeAll(staffs);
    }

    public String getMinister() {
        return minister;
    }
}

改进手法:函数搬移

/**
 * 部门信息
 */
public class Department {
    private final String minister; // 部长

    private final String name; // 部门名称

    private List<Staff> staffs = new ArrayList<>(); // 部门员工列表

    public Department(String minister, String name) {
        this.minister = minister;
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public List<Staff> getStaffs() {
        return Collections.unmodifiableList(staffs);
    }

    public void addStaffs(List<Staff> staffs) {
        this.staffs.addAll(staffs);
    }

    public void deleteStaffs(List<Staff> staffs) {
        this.staffs.removeAll(staffs);
    }

    public String getMinister() {
        return minister;
    }

    public long getStaffNumOfGender(Gender male) {
        return getStaffs().stream().filter(staff -> male.equals(staff.getGender())).count();
    }
}
public class Audit {
    /**
     * 审计男女比率
     *
     * @param department 部门信息
     * @return 男女比例 (没有女员工返回-1.0)
     */
    public double auditGenderRatio(Department department) {
        long maleNums = department.getStaffNumOfGender(Gender.MALE);
        long femaleNums = department.getStaffNumOfGender(Gender.FEMALE);

        // 女员工数为0时 返回-1
        if (femaleNums == 0) {
            return -1.0;
        }
        return maleNums / (femaleNums * 1.0);
    }
}
/**
 * hrbp管理
 */
public class Hrbp {
    private final List<Department> departments; // 部门列表

    public Hrbp(List<Department> departments) {
        this.departments = departments;
    }

    /**
     * 统计所有部门下(男/女)性别员工数量
     *
     * @return (男/女)性别员工总数
     */
    public long getStaffNumOfGender(Gender gender) {
        return departments.stream().mapToLong(department -> department.getStaffNumOfGender(gender)).sum();
    }
}

纯数据类必须要重构吗?找遍所有「取值/设值」函数没有发现可搬移的行为,怎么办?

并非所有纯稚的数据类都需要重构,先分析其被调用点的行为特点!纯稚的数据类有其使用场景,比如DTO这种贫血模型。

操作手法

操作快捷键(推荐)Ctrl+Alt+Shift+T(或:鼠标右键“Refactor”)
封装变量Encapsulate Fields
提取函数Ctrl+Atl+MExtract Method
移除设值函数(用内联移除)Ctrl+Alt+NInline Method
移除设值函数(直接删除)Alt+DelSafe Delete
搬移函数F6Move Instance Method