曾用名:自封装字段(Self-Encapsulate Field)
曾用名:封装字段(Encapsulate Field)
let defaultOwner = {firstName: "Martin", lastName: "Fowler"};
let defaultOwnerData = {firstName: "Martin", lastName: "Fowler"};
export function defaultOwner() {return defaultOwnerData;}
export function setDefaultOwner(arg) {defaultOwnerData = arg;}
重构的作用就是调整程序中的元素。函数相对容易调整一些,因为函数只有一种用法,就是调用。在改名或搬移函数的过程中,总是可以比较容易地保留旧函数作为转发函数(即旧代码调用旧函数,旧函数再调用新函数)。这样的转发函数通常不会存在太久,但的确能够简化重构过程。
如果数据的可访问范围变大,重构的难度就会随之增大,这也是说全局数据是大麻烦的原因。
如果想要搬移一处被广泛使用的数据,最好的办法往往是先以函数形式封装所有对该数据的访问。
封装能提供一个清晰的观测点,可以由此监控数据的变化和使用情况,还可以轻松地添加数据被修改时的验证或后续逻辑。数据的作用域越大,封装就越重要。处理遗留代码时,一旦需要修改或增加使用可变数据的代码,可以借机把这份数据封装起来,从而避免继续加重耦合一份已经广泛使用的数据。
面向对象方法如此强调对象的数据应该保持私有(private),背后也是同样的原理。每当看见一个公开(public)的字段时,考虑使用封装变量来缩小其可见范围。
封装数据很重要,不过,不可变数据更重要。如果数据不能修改,就根本不需要数据更新前的验证或者其他逻辑钩子。
对数据结构的引用做封装,能控制对该数据结构的访问和重新赋值;但并不能控制对结构内部数据项的修改。
控制结构内部数据的修改方法:
取值函数中返回数据的一份副本(保护源数据,防止源数据变化导致的意外事故)
阻止对数据的修改(禁止对数据结构内部的数值做任何修改)
数据封装很有价值,但往往并不简单。到底应该封装什么,以及如何封装,取决于数据被使用的方式,以及想要修改数据的方式。
let a = height * width;
let area = height * width;
好的命名是整洁编程的核心。变量名起得好,变量可以很好地解释一段程序在干什么。
变量使用范围越广,名字的好坏就越重要。
function amountInvoiced(startDate, endDate) {...}
function amountReceived(startDate, endDate) {...}
function amountOverdue(startDate, endDate) {...}
function amountInvoiced(aDateRange) {...}
function amountReceived(aDateRange) {...}
function amountOverdue(aDateRange) {...}
一组数据项(作为函数参数)总是同时出现(这样一组数据就是所谓的数据泥团)。
将数据组织成结构是一件有价值的事,因为这让数据项之间的关系变得明晰。使用新的数据结构,参数的参数列表也能缩短。
function base(aReading) {...}
function taxableCharge(aReading) {...}
function calculateBaseCharge(aReading) {...}
class Reading {
base() {...}
taxableCharge() {...}
calculateBaseCharge() {...}
}
类,在大多数现代编程语言中都是基本的构造。它们把数据与函数捆绑到同一个环境中,将一部分数据与函数暴露给其他程序元素以便协作。
如果发现一组函数相互操作同一块数据(通常是将这块数据作为参数传递给函数),可以将这几个函数足见2一个类。类能明确地给这些函数提供一个共用的环境,在对象内部调用这些函数可以少传许多参数,从而简化函数调用。
在软件中经常有这类操作:一个数据项在一个函数中处理加工后,有进入到其他函数中继续处理加工,经过几番处理,才得到最终需要的结果(可能我们的初衷不这样,但由于后期新需求、功能的添加才形成这种现象)。
一个方式是采用数据变换(transform)函数:这种函数接受源数据作为输入,计算出所有的派生数据,将派生数据以字段形式填入输出数据。
函数组合成变换的替代方案是函数组合成类(144),后者的做法是先用源数据创建一个类,再把相关的计算逻辑搬移到类中。
const orderData = orderString.split(/\s+/);
const productPrice = priceList[orderData[0].split("-")[1]];
const orderPrice = parseInt(orderData[1]) * productPrice;
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];
}
一段代码在同时处理两件不同的事。
最简洁的拆分方法之一,就是把一大段行为分成顺序执行的两个阶段。