重构八:重新组织数据

501 阅读5分钟

Self Encapsulate Field(自封装值域)

  • 你直接访问一个字段(Field),但与值域之间的耦合关系主键变得笨拙。

  • 为这个值域简历get/set函数,并且只以这些函数来访问值域。

    • 优点:子类可以通过覆写一个函数而改变获取数据的途径;支撑更灵活的数据管理方式
  • 直接访问变量

    • 优点:容易阅读
  • 对于构造函数,应直接访问值域,要不就建立另一个独立的构造函数,在有subclass时,价值就体现出来了。

Replace Data Value with Object(以对象取代数据值)

  • 你有一笔数据项(data item),需要额外的数据和行为。变为组合关系

image-20211207103334717.png

  • 做法:

    • 为待替换数值新建一个类,在新类中加入这个字段的取值函数,再加上一个接受此字段为参数的构造函数
    • 将源类中的待替换树脂字段的类型改为前面新建的类
    • 修改源类中该字段的取值函数,令他调用新类的取值函数

Change Value to Reference(将实值对象改为引用对象)

你有一个class,衍生出许多相等实体,你希望将他们替换成单一对象,组合变关联

image-20211207104745753.png

Change Reference to Value(将引用对象改为值对象)

  • 如果引用对象开始变得难用,可以将它改为值对象
  • 检查重构目标是否为不可变对象,或是否可修改为不可变对象,如果不能改为不可变的,则放弃本项重构。
  • 使引用对象变得不可更改的简单做法是去掉setXXX()方法
  • 重写对象的equals和hashcode方法,使new XXX().equals(new XXX()) == true;

Replace Array with Object(以对象取代数组)

  • 如果数组中的元素代表不同的东西,则将数组替换成对象,按照字段方式方便读取。

  • 方式:

    • 新建一个类表示数组拥有的所有信息,并在其中以一个public字段保存原先的数组
    • 逐一为数组元素添加取值/设值函数
    • 当对数组的访问都有访问函数时,将数组改为private
    • 当所有元素都有相应字段后,删除该数组

Duplicate Observed Data(复制“被监视数据”)

image-20211207141242125.png

  • 动机:一个良好的系统,应该将处理用户界面和处理业务逻辑的代码分开,

    • 你可能需要使用不同的用户界面来表现相同的业务逻辑,如果同时承担两种责任,用户界面会变得过分复杂
    • 与GUI隔离之后,领域对象的维护和演化都会更容易,甚至可以让不同的开发者负责不同部分的开发
    • 行为可以轻易地划分,但数据往往不能,需要使用多层架构系统来保持他们同步
  • Interval为被观察者Observable,继承Observable;Interval Window为观察者Observer,实现Observer接口

Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)

image-20211207211345719.png

  • 两个类都需要使用对方的特性,但其间只有一条单向连接
  • 在被引用端增加一个字段,用以保存反向指针Order为引用端,Customer为被引用端

Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)

image-20211209152007748.png

  • 和上面相反

Replace Magic Number with Symbolic Constant(以字面常量取代魔法数)

  • 创建一个常量,根据其意义为他命名,并将字面数值替换为这个常量

    int a = b * c + 10.5;中10.5使用常量表示,如下 :

    int a = b * c + CONSTANT;

    static fianl double CONSTANT = 10.5

Encapsulate Field(封装字段)

  • 当类中存在一个public字段,将它声明为private,并提供相应的访问函数

Encapsulate Collection(封装集合)

  • 让这个函数返回集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数

image-20211209163914492.png

以下面集合为例

// 保证courses只能通过类方法进行修改,而不能在外部修改
public class Person {
    /**
     * <对集合进行初始化>
     */
    private Set<Course> courses = new HashSet<>();
    // get方法返回不可变副本
    public Set<Course> getCourses() {
        return Collections.unmodifiableSet(courses);
    }
    // 设置函数已经不再需要,所有对set的操作都可保证不受外部影响
    // public void initializeCourses(Set<Course> courses) {
    //     Iterator iter = courses.iterator();
    //     courses.addAll(courses);
    // }
    
    // 添加元素
    public void addCourse(Course course) {
        this.courses.add(course);
    }
    // 移除元素
    public void removeCourse(Course course) {
        this.courses.remove(course);
    }
    // 类行为方法
    int numberOfAdvancedCourse() {
        Iterator<Course> iterator = getCourses().iterator();
        int count = 0;
        while(iterator.hasNext()) {
            Course next = iterator.next();
            if (next.isAdvanced()) {
                count++;
            }
        }
        return count;
    }
}

Replace Record with Data Class(以数据类取代记录)

  • 面对传统编程环境中的记录结构,为改记录创建一个“哑”数据对象
  • 常用手法:Replace Array with Object

Replace Type Code with Class(以类取代类型码)

image-20211213112838230.png

Replace Type Code with Subclasses(以子类取代类型码)

image-20211213112932284.png

  • 如果类型码不会影响宿主类的行为,可以使用Replace Type Code with Class处理
  • 如果影响宿主类行为,则使用多态来处理行为
  • 建立子类后,使用push down method 和 push down field将相关字段和函数推到子类中去
  • 将父类的构造函数改为工厂函数

Replace Type Code with State/Strategy(以state/strategy取代类型码)

image-20211213115133018.png

  • 你有一个类型码,它会影响类的行为,但你无法通过继承手法消除它
  • 以Emplyee作为源类,将EmployeeType作为变量放入源类中,EmployeeType作为抽象类,抽象类负责创建子类,各类型作为子类继承。
  • 使用State/Strategy模式进行构造,这两个模型的整改步骤是相通的。

Replace Subclass with Fields(以字段取代子类)

image-20211213151133530.png

  • 你的各个子类的唯一差别只在“返回常量数据”的函数身上
  • 和替换为子类相反,子类中只有常量函数,实在没有足够的存在价值。