• CH06_第一组重构(下)


    封装变量(Encapsulate Variable | 132)

    曾用名:自封装字段(Self-Encapsulate Field)

    曾用名:封装字段(Encapsulate Field)

    在这里插入图片描述

    let defaultOwner = {firstName: "Martin", lastName: "Fowler"};
    
    • 1
    let defaultOwnerData = {firstName: "Martin", lastName: "Fowler"};
    export function defaultOwner() {return defaultOwnerData;}
    export function setDefaultOwner(arg) {defaultOwnerData = arg;}
    
    • 1
    • 2
    • 3

    动机

    重构的作用就是调整程序中的元素。函数相对容易调整一些,因为函数只有一种用法,就是调用。在改名或搬移函数的过程中,总是可以比较容易地保留旧函数作为转发函数(即旧代码调用旧函数,旧函数再调用新函数)。这样的转发函数通常不会存在太久,但的确能够简化重构过程。

    如果数据的可访问范围变大,重构的难度就会随之增大,这也是说全局数据是大麻烦的原因。

    如果想要搬移一处被广泛使用的数据,最好的办法往往是先以函数形式封装所有对该数据的访问。

    封装能提供一个清晰的观测点,可以由此监控数据的变化和使用情况,还可以轻松地添加数据被修改时的验证或后续逻辑。数据的作用域越大,封装就越重要。处理遗留代码时,一旦需要修改或增加使用可变数据的代码,可以借机把这份数据封装起来,从而避免继续加重耦合一份已经广泛使用的数据。

    面向对象方法如此强调对象的数据应该保持私有(private),背后也是同样的原理。每当看见一个公开(public)的字段时,考虑使用封装变量来缩小其可见范围。

    封装数据很重要,不过,不可变数据更重要。如果数据不能修改,就根本不需要数据更新前的验证或者其他逻辑钩子。

    做法

    • 创建封装函数,在其中访问和更新变量值。
    • 执行静态检查。
    • 逐一修改使用该变量的代码,将其改为调用合适的封装函数。每次替换之后,执行测试。
    • 限制变量的可见性。
    • 测试。
    • 如果变量的值是一个记录,考虑使用封装记录(162)。

    封装值

    数据结构的引用做封装,能控制对该数据结构的访问和重新赋值;但并不能控制对结构内部数据项的修改。

    控制结构内部数据的修改方法:

    • 取值函数中返回数据的一份副本(保护源数据,防止源数据变化导致的意外事故)

    • 阻止对数据的修改(禁止对数据结构内部的数值做任何修改)

    数据封装很有价值,但往往并不简单。到底应该封装什么,以及如何封装,取决于数据被使用的方式,以及想要修改数据的方式。

    变量改名(Rename Variable | 137)

    在这里插入图片描述

    let a = height * width;
    
    • 1
    let area = height * width; 
    
    • 1

    动机

    好的命名是整洁编程的核心。变量名起得好,变量可以很好地解释一段程序在干什么。

    变量使用范围越广,名字的好坏就越重要。

    机制

    • 如果变量被广泛使用,考虑运用封装变量(132)将其封装起来。
    • 找出所有使用该变量的代码,逐一修改。(已发布变量,不能进行这个重构)
    • 测试。

    引入参数对象(Introduce Parameter Object | 140)

    在这里插入图片描述

    function amountInvoiced(startDate, endDate) {...}
    function amountReceived(startDate, endDate) {...}
    function amountOverdue(startDate, endDate) {...}
    
    • 1
    • 2
    • 3
    function amountInvoiced(aDateRange) {...}
    function amountReceived(aDateRange) {...}
    function amountOverdue(aDateRange) {...}
    
    • 1
    • 2
    • 3

    动机

    一组数据项(作为函数参数)总是同时出现(这样一组数据就是所谓的数据泥团)。

    将数据组织成结构是一件有价值的事,因为这让数据项之间的关系变得明晰。使用新的数据结构,参数的参数列表也能缩短。

    做法

    • 如果暂时还没有一个合适的数据结构,就创建一个
    • 测试
    • 使用改变函数声明(124)给原来的函数新增一个参数,类型是新建的数据结构
    • 测试
    • 调整所以调用者,传入新数据结构的适当实列。修改每一处,执行测试
    • 用新数据结构中的每一项元素,逐一取代参数列表中与之对应的参数项,然后删除原来的参数。测试

    函数组合成类(Combine Functions into class | 144)

    在这里插入图片描述

    function base(aReading) {...}
    function taxableCharge(aReading) {...}
    function calculateBaseCharge(aReading) {...}
    
    • 1
    • 2
    • 3
    class Reading {
        base() {...}
        taxableCharge() {...}
        calculateBaseCharge() {...}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    动机

    类,在大多数现代编程语言中都是基本的构造。它们把数据与函数捆绑到同一个环境中,将一部分数据与函数暴露给其他程序元素以便协作。

    如果发现一组函数相互操作同一块数据(通常是将这块数据作为参数传递给函数),可以将这几个函数足见2一个类。类能明确地给这些函数提供一个共用的环境,在对象内部调用这些函数可以少传许多参数,从而简化函数调用。

    做法

    • 运用封装记录(162)对多个函数共用的数据记录加以封装。
    • 对于使用该记录结构的每个函数,运用搬移函数(198)将其移入新类。(如果函数调用时传入的参数已经是新类的成员,则从参数列表中去除之。)
    • 用以处理该数据记录的逻辑可以用提炼函数(106)提炼出来,并移入新类。

    函数组合成变换(Combine Functions into Transfrom | 149)

    在这里插入图片描述

    动机

    在软件中经常有这类操作:一个数据项在一个函数中处理加工后,有进入到其他函数中继续处理加工,经过几番处理,才得到最终需要的结果(可能我们的初衷不这样,但由于后期新需求、功能的添加才形成这种现象)。

    一个方式是采用数据变换(transform)函数:这种函数接受源数据作为输入,计算出所有的派生数据,将派生数据以字段形式填入输出数据。

    函数组合成变换的替代方案是函数组合成类(144),后者的做法是先用源数据创建一个类,再把相关的计算逻辑搬移到类中。

    做法

    • 创建一个变换函数,输入参数是需要变换的记录,并直接返回该记录的值。
    • 挑选一块逻辑,将其主体一如变换函数中,把结果作为字段添加到输出记录中。修改客户端代码,令其使用这个新字段
    • 测试
    • 针对其他相关的计算逻辑,重复上述步骤

    拆分阶段(Split Phase | 154)

    在这里插入图片描述

    const orderData = orderString.split(/\s+/);
    const productPrice = priceList[orderData[0].split("-")[1]];
    const orderPrice = parseInt(orderData[1]) * productPrice;
    
    • 1
    • 2
    • 3
    const orderRecord = parseOrder(order);
    const orderPrice = price(orderRecord, priceList);
    function parseOrder(aString) {
    const values = aString.split(/\s+/);
        return ({
            productID: values[0].split("-")[1],
            quantity: parseInt(values[1]),
        });
    }
    function price(order, priceList) {
    	return order.quantity * priceList[order.productID];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    动机

    一段代码在同时处理两件不同的事。

    最简洁的拆分方法之一,就是把一大段行为分成顺序执行的两个阶段。

    做法

    • 将第二阶段的代码提炼成独立的函数
    • 测试
    • 引入一个中转数据结构,将其作为参数添加到提炼出的新函数的参数中
    • 测试
    • 逐一检查提炼出的“第二阶段函数”的每个参数。如果某个参数被第一阶段用到,将其移入中转数据结构。每次搬移之后都要执行测试。
    • 对第一阶段的代码运用提炼函数(106),让提炼出的函数返回中转数据结构。
  • 相关阅读:
    c语言开篇---跟着视频学C语言
    CSDN 网络技能树学习打卡第1天
    Linux下安装Foldseek并从蛋白质的PDB结构中获取 3Di Token 和 3Di Embedding
    动态规划:得到目标货币的方法数(有限张货币 + 面值相同的货币相同)
    java-php-python--关爱留守儿童志愿者管理系统-计算机毕业设计
    我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制
    从零开始 Spring Boot 13:参数校验
    kingbase 创建用户报错
    [激光原理与应用-21]:《激光原理与技术》-7- 激光技术大汇总与总体概述
    微信小程序实现上下手势滑动切换
  • 原文地址:https://blog.csdn.net/qq_27953479/article/details/132774278