重新组织数据
-
拆分变量
一个变量只承担一种责任
function distanceTravelled(){ let acc = 0 acc = '' }function distanceTravelled(){ let acc = 0 let str = '' }做法
- 在待分解变量的声明及其第一次被赋值处,修改其名称
- 如果可能的话,把新变量声明为不可修改
- 以该变量的第二次赋值动作为分解,修改此前对该变量的引用,引用新变量
function distanceTravelled(scenario, time){ let result let acc = scenario.primaryForce / scenario.mass let primaryTime = Math.min(time, scenario.delay) result = 0.5 * acc * primaryTime * primaryTime let secondTime = time - scenario.delay if(secondTime > 0){ let primaryVelocity = acc * scenario.delay acc = (scenario.primaryForce + scenario.secondForce) / scenario.mass result += primaryVelocity * secondTime + 0.5 * acc * secondTime * secondTime } return result }function distanceTravelled(scenario, time){ let result let primaryAcceleration = scenario.primaryForce / scenario.mass let primaryTime = Math.min(time, scenario.delay) result = 0.5 * primaryAcceleration * primaryTime * primaryTime let secondTime = time - scenario.delay if(secondTime > 0){ let primaryVelocity = primaryAcceleration * scenario.delay let secondaryAcceleration = (scenario.primaryForce + scenario.secondForce) / scenario.mass result += primaryVelocity * secondTime + 0.5 * secondaryAcceleration * secondTime * secondTime } return result } -
字段改名
将不符合语义的字段名改为更加贴切的名字
const organization = { name: 'Acme', country: 'GB' }class Organization { constructor(data){ this._title = data.title || data.name } get title() {return this._title } set title(title) {this._title = title} }做法
- 如果记录未封装,则先封装记录
- 对字段改名,并将引用处同步更新
const organization = { name: 'Acme', country: 'GB' }class Organization { constructor(data){ this._title = data.title || data.name this._country = data.country } get title() {return this._title } set title(title) {this._title = title} get country() {return this._country} set country(country) {this._country = country} } const organization = new Organization({ name: 'Acme', country: 'GB' }) -
以查询取代派生变量
可变对数据是软件中最大对错误源头,对数据对修改常常导致代码各个部分以丑陋的形式互相耦合
class ProductionPlan { constructor(adjustments){ this._adjustments = adjustments this._production = this._adjustments.reduce((prev, item) => prev + item.amount, 0) } get production(){return this._production} applyAdjustment(anAdjustment){ this._adjustments.push(anAdjustment) this._production += anAdjustment.amount } }class ProductionPlan { constructor(adjustments){ this._adjustments = adjustments } get production(){return this._adjustments.reduce((prev, item) => prev + item.amount, 0)} applyAdjustment(anAdjustment){ this._adjustments.push(anAdjustment) } }做法
- 识别出所有对变量做更新的地方
- 新建一个函数用于计算该变量的值
class ProductionPlan { constructor(adjustments){ this._adjustments = adjustments this._production = this._adjustments.reduce((prev, item) => prev + item.amount, 0) } get production(){return this._production} applyAdjustment(anAdjustment){ this._adjustments.push(anAdjustment) this._production += anAdjustment.amount } }class ProductionPlan { constructor(adjustments){ this._adjustments = adjustments } get production(){return this._adjustments.reduce((prev, item) => prev + item.amount, 0)} applyAdjustment(anAdjustment){ this._adjustments.push(anAdjustment) } } -
将引用对象改为值对象(将值对象改成引用对象)
更新一个引用对象的属性值,直接替换整个内部对象
class Person { constructor(){ this._tele = new TelephoneNumber() } } class Tele{...}class Person { set officeAreaCode(arg){return this._tele = new Tele() } set officeNumber(arg){return this._tele = new Tele() } } class Tele{...}做法
- 检查重构目标是否为不可变的对象,或者是否修改为不可变对象
- 修改对象中的设值函数,创建一个新的对象
class Person { constructor(name){ this._name = name this._telephoneNumber = new TelephoneNumber() } get name(){return this._name } get telephoneNumber(){return this._telephoneNumber.toString() } get officeAreaCode(){return this._telephoneNumber.areaCode } set officeAreaCode(arg){return this._telephoneNumber.areaCode = arg } get officeNumber(){return this._telephoneNumber.number } set officeNumber(arg){return this._telephoneNumber.number = arg } } class TelephoneNumber{ get number(){return this._number} set number(arg){return this._number = arg} get areaCode(){return this._areaCode } set areaCode(arg){return this._areaCode = arg} toString(){return `(${this._areaCode}) ${this._number}`} }class Person { constructor(name){ this._name = name } get name(){return this._name } get telephoneNumber(){return this._telephoneNumber.toString() } get officeAreaCode(){return this._telephoneNumber.areaCode } set officeAreaCode(arg){return this._telephoneNumber = new TelephoneNumber(arg, this.officeNumber) } get officeNumber(){return this._telephoneNumber.number } set officeNumber(arg){return this._telephoneNumber = new TelephoneNumber(this.areaCode, arg) } } class TelephoneNumber{ constructor(areaCode, number){ this._areaCode = areaCode this._number = number } get number(){return this._number} get areaCode(){return this._areaCode } toString(){return `(${this._areaCode}) ${this._number}`} } -
将值对象改成引用对象(将引用对象改为值对象)
过多的副本会难以维护,可以创建一个仓库存储
class Order { constructor(data){ this._customer = new Customer(data.customer) } } class Customer {}let _repository = {} _repository.customer = new Map() function registerCustomer(id){ if(!_repository.customer.has(id)){ _repository.customer.set(id, new Customer(id)) } return findCustomer(id) } function findCustomer(id){ return _repository.customer.get(id) } class Order { constructor(data){ this._customer = registerCustomer(data.customer) } } class Customer {}做法
- 为相关的对象创建一个仓库
- 确保构造函数有办法找到关联对象的正确实例
- 修改宿主对象的构造函数,令其从仓库中获取关联对象
class Order { constructor(data){ this._number = data.number; this._customer = new Customer(data.customer) } get customer(){return this._customer} } class Customer { constructor(id){ this._id = id } get id(){return this._id} }let _repositoryData function initialize(){ _repositoryData = {} _repositoryData.customer = new Map() } function registerCustomer(id){ if(!_repositoryData.customer.has(id)){ _repositoryData.customer.set(id, new Customer(id)) } return findCustomer(id) } function findCustomer(id){ return _repositoryData.customer.get(id) } initialize() class Order { constructor(data){ this._number = data.number; this._customer = registerCustomer(data.customer) } get customer(){return this._customer} } class Customer { constructor(id){ this._id = id } get id(){return this._id} }
简化条件逻辑
-
分解条件表达式
把复杂的条件表达式分解成多个独立的函数
function price(){ ... if(aDate.isSummer || aDate.isSpring){ charge = quantity * aPlan.summerRate }else{ charge = quantity * aPlan.rate } ... }function price(aDate, aPlan){ ... return summerOrSpring() ? summerCharge() : orderCharge() function summerOrSpring(){...} function summerCharge(){...} function orderCharge(){...} }做法
- 对条件判断和每个条件分支都使用提炼函数
function price(aDate, aPlan){ let charge let quantity = 100 if(aDate.isSummer || aDate.isSpring){ charge = quantity * aPlan.summerRate }else{ charge = quantity * aPlan.rate } return charge }function price(aDate, aPlan){ let quantity = 100 return summerOrSpring() ? summerCharge() : orderCharge() function summerOrSpring(){ return aDate.isSummer || aDate.isSpring } function summerCharge(){ return quantity * aPlan.summerRate } function orderCharge(){ return quantity * aPlan.rate } } -
合并条件表达式
把相同返回的条件合并成一处
if(state == 1) return 0 if(start == 2) return 0 return 1if(state == 1 || start == 2) return 0 return 1做法
- 确定条件表达式没有副作用
- 使用适当的运算符合并
if(state == 1) return 0 if(start == 2) return 0 if(end == 3) return 0 return 1if(state == 1 || start == 2 || end == 3) return 0 return 1 -
以卫语句取代嵌套条件表达式
多层的嵌套判断会减低可读性,可以使用卫语句进行提前返回
function payAmount(employee){ if(employee.isSeparated){ ... }else{ if(employee.isRetired){ ... }else{ ... } } }function payAmount(employee){ if(employee.isSeparated) return if(employee.isRetired) return return }做法
- 选中外层需要被替换的条件逻辑,替换成卫语句
function payAmount(employee){ let result if(employee.isSeparated){ result = {amount: 0, reasonCode: 'SEP'} }else{ if(employee.isRetired){ result = {amount: 0, reasonCode: 'RET'} }else{ result = {amount: 1000, reasonCode: ''} } } return result }function payAmount(employee){ if(employee.isSeparated) return {amount: 0, reasonCode: 'SEP'} if(employee.isRetired) return {amount: 0, reasonCode: 'RET'} return {amount: 1000, reasonCode: ''} } -
以多态取代条件表达式
通过类的多态去改善比较复杂的条件表达式
class Bird { constructor(name, type){ switch (bird.type){ case 'E': this.plumage = 'e' case 'N': this.plumage = 'n' default: this.plumage = 'unknown' } } }class Bird { get plumage(){ return 'unknown' } } class E extends Bird{ get plumage(){ return 'e' } } class N extends Bird{ get plumage(){ return 'n' } } function createBird(...arg){ switch (arg[1]){ case 'E': return new E(...arg); case 'N': return new N(...arg); default: return new Bird(...arg); } }做法
- 如果现有的类不具备多态行为,就用工厂模式创建
- 在调用方代码中使用工程函数获得对象实例
- 将带有条件逻辑的函数移动到超类中
- 任选一个子类,在其中创建一个函数,使其复写超类中容纳条件表达式的那个函数
- 将于该子类相关的条件表达式分支复制到新函数中
- 处理完后将超类的函数声明为抽象函数
function plumages(birds){ return new Map(birds.map(b => [b.name, b.plumage])) } function speeds(birds){ return new Map(birds.map(b => [b.name, airSpeedVelocity(b)])) } function plumage(bird){ switch (bird.type){ case 'E': return 'a' case 'A': return bird.counts > 2 ? 't' : 'a' case 'N': return bird.voltage > 100 ? 's' : 'b' default: return 'unknown' } } function airSpeedVelocity(bird){ switch (bird.type){ case 'E': return 35 case 'A': return 40 - bird.counts case 'N': return bird.voltage / 10 + 10 default: return null } } class Bird { constructor(name, type, counts, voltage){ this.name = name this.type = type this.counts = counts this.voltage = voltage this.plumage = plumage(this) } }function plumages(birds){ return new Map(birds.map(b => [b.name, b.plumage])) } function speeds(birds){ return new Map(birds.map(b => [b.name, b.airSpeedVelocity])) } class Bird { constructor(name, type, counts, voltage){ this.name = name this.type = type this.counts = counts this.voltage = voltage } get plumage(){ return 'unknown' } get airSpeedVelocity(){ return null } } class E extends Bird{ get plumage(){ return 'a' } get airSpeedVelocity(){ return 35 } } class A extends Bird{ get plumage(){ return this.counts > 2 ? 't' : 'a' } get airSpeedVelocity(){ return 40 - this.counts } } class N extends Bird{ get plumage(){ this.voltage > 100 ? 's' : 'b' } get airSpeedVelocity(){ return this.voltage / 10 + 10 } } function createBird(...arg){ switch (arg[1]){ case 'E': return new E(...arg); case 'A': return new A(...arg); case 'N': return new N(...arg); default: return new Bird(...arg); } }function rating(voyage, history){ const vpf = voyageProfitFactor(voyage, history) const vr = voyageRisk(voyage) const chr = captainHistoryRisk(voyage, history) if(vpf * 3 > (vr + chr * 2)) return 'A' return 'B' } function voyageRisk(voyage){ let result = 1 if(voyage.length > 4) result += 2 if(voyage.length > 8) result += voyage.length - 8 if(['china', 'east-indies'].includes(voyage.zone)) result += 4 return Math.max(result, 0) } function captainHistoryRisk(voyage, history){ let result = 1 if(history.length < 5) result += 4 result += history.filter(v => v.profit < 0).length if(voyage.zone === 'china' && hasChina(history)) result -= 2 return Math.max(result, 0) } function hasChina(history) { return history.some(v => v.zone === 'china') } function voyageProfitFactor(voyage, history){ let result = 2 if(voyage.zone === 'china') result += 1 if(voyage.zone === 'east-indies') result += 1 if(voyage.zone === 'china' && hasChina(history)){ result += 3 if(history.length > 10) result += 1 if(voyage.length > 12) result += 1 if(voyage.length > 18) result -= 1 }else{ if(history.length > 8) result += 1 if(voyage.length > 14) result -= 1 } return result } const voyage = { zone: 'west-indies', length: 10 } const history = [ { zone: 'east-indies', profit: 5 }, { zone: 'west-indies', profit: 15 }, { zone: 'china', profit: -2 }, { zone: 'west-africa', profit: 7 } ] console.log(rating(voyage, history))function rating(voyage, history){ return createRating(voyage, history).value } function createRating(voyage, history){ if(voyage.zone === 'china' && history.some(v => v.zone === 'china')) return new ExperienceChinaRating(voyage, history) return new Rating(voyage, history) } class Rating { constructor(voyage, history){ this.voyage = voyage this.history = history } get value(){ const vpf = this.voyageProfitFactor const vr = this.voyageRisk const chr = this.captainHistoryRisk if(vpf * 3 > (vr + chr * 2)) return 'A' return 'B' } get voyageProfitFactor(){ let result = 2 if(this.voyage.zone === 'china') result += 1 if(this.voyage.zone === 'east-indies') result += 1 result += this.historyLengthFactor result += this.voyageLengthFactor return result } get voyageLengthFactor(){ return this.voyage.length > 14 ? 1 : 0 } get historyLengthFactor(){ return this.history.length > 8 ? 1 : 0 } get voyageRisk(){ let result = 1 if(this.voyage.length > 4) result += 2 if(this.voyage.length > 8) result += this.voyage.length - 8 if(['china', 'east-indies'].includes(this.voyage.zone)) result += 4 return Math.max(result, 0) } get captainHistoryRisk(){ let result = 1 if(this.history.length < 5) result += 4 result += this.history.filter(v => v.profit < 0).length return Math.max(result, 0) } } class ExperienceChinaRating extends Rating{ get captainHistoryRisk(){ const result = super.captainHistoryRisk - 2 return result } get voyageProfitFactor(){ return super.voyageProfitFactor + 3 } get voyageLengthFactor(){ let result = 0 if(this.voyage.length > 12) result += 1 if(this.voyage.length > 18) result -= 1 return result } get historyLengthFactor(){ return this.history.length > 10 ? 1 : 0 } } const voyage = { zone: 'west-indies', length: 10 } const history = [ { zone: 'east-indies', profit: 5 }, { zone: 'west-indies', profit: 15 }, { zone: 'china', profit: -2 }, { zone: 'west-africa', profit: 7 } ] console.log(rating(voyage, history)) -
引入特例
将多个相同的特殊情况取值收拢于一处
function customerName(aCustomer){ if(aCustomer.toString() === 'unknown') return 'occupant' return aCustomer.name } class Customer { toString() { return this.name || 'unknown' } }function customerName(aCustomer){ return aCustomer.name } class Customer { toString() { return this.name || 'unknown' } } const customer = enrichCustomer(new Customer()) function enrichCustomer(aCustomer){ const unknownCustomer = { name: 'occupant', } if(aCustomer.toString() === 'unknown') return unknownCustomer return aCustomer }做法
- 给重构目标添加检查特例的属性,令其返回false
- 创建一个特例对象,其中只有检查特例的属性,返回true
- 对“与特例值做对比”提炼函数,并确保客户端使用这个新函数
- 将新的特例对象引入代码中,可以从函数调用中返回,也可以在变换函数中生成
- 修改特例对比函数的主题,在其直接使用检查特例的属性
- 使用函数组合成类、函数组合成变换,把通用的特例处理逻辑搬移到新建的特例对象中
- 对特例对比函数使用内联函数,将其内联到仍然需要的地方
function customerName(aCustomer){ if(aCustomer.toString() === 'unknown') return 'occupant' return aCustomer.name } function customerPlan(aCustomer){ if(aCustomer.toString() === 'unknown') return 'basic plan' return aCustomer.plan } class Customer { constructor(name, plan) { this.name = name this.plan = plan } toString() { return this.name || 'unknown' } }function customerName(aCustomer){ return aCustomer.name } function customerPlan(aCustomer){ return aCustomer.plan } class Customer { constructor(name, plan) { this.name = name this.plan = plan } toString() { return this.name || 'unknown' } } const customer = enrichCustomer(new Customer()) function enrichCustomer(aCustomer){ const unknownCustomer = { name: 'occupant', plan: 'basic plan' } if(aCustomer.toString() === 'unknown') return unknownCustomer return aCustomer } -
引入断言
通过断言告诉阅读者,程序执行到这一点是,对当前状态做了何种假设 做法
- 如果你发现代码假设的某个条件始终为真,就可以加入一个断言说明情况
重构API
-
将查询函数和修改函数分离
区分处有副作用和无副作用的函数
function setOffAlarms(){//setTimeout} function alertForMiscreant(people) { for (const p of people) { if(p === 'oo'){ setOffAlarms() return p } } return '' }function alertForMiscreant(people) { if(findMiscreant(people) !== '') setOffAlarms() } function findMiscreant(people) {// find }做法
- 复制整个函数,将其作为一个查询命名
- 从新建的查询中去掉所有存在副作用的语句
- 查找调用函数的地方,替换
const people = ['xx', 'yy', 'zz', 'oo', 'zzz', 'cc'] function setOffAlarms(){ setTimeout(() => { console.log('!!!') }, 100) } function alertForMiscreant(people) { for (const p of people) { if(p === 'oo'){ setOffAlarms() return p } if(p === 'cc'){ setOffAlarms() return p } } return '' }function alertForMiscreant(people) { if(findMiscreant(people) !== '') setOffAlarms() } function findMiscreant(people) { for (const p of people) { if(p === 'oo' || p === 'cc'){ return p } } return '' } const found = findMiscreant(people) alertForMiscreant(people) -
函数参数化
如果两个函数逻辑相似,只是字面量值不同,则可以合成一个函数,以参数的形式传入不同的值消除重复
function tenPercentRaise(salary){ return salary * 1.1 } function fivePercentRaise(salary){ return salary * 1.05 }function raise(salary, factor=0){ return salary * (factor + 1) } function tenPercentRaise(salary){ return raise(salary, 0.1) } function fivePercentRaise(salary){ return raise(salary, 0.05) }做法
- 从一组相似的函数中选择一个
- 把需要作为参数传入的字面量添加到参数列表中
- 修改函数所有的调用处,更新参数传入
- 修改函数体,使其使用新的参数
function baseCharge(usage){ if(usage < 0) return 0 return topBand(usage) * 0.07 + bottomBand(usage) * 0.03 + middleBand(usage) * 0.05 } function bottomBand(usage){ return Math.min(usage, 100) } function topBand(usage){ return usage > 200 ? usage - 200 : 0 } function middleBand(usage){ return usage > 100 ? Math.min(usage, 200) - 100 : 0 }function baseCharge(usage){ if(usage < 0) return 0 return withinBand(usage, 0, 100) * 0.03 + withinBand(usage, 100, 200) * 0.05 + withinBand(usage, 200, Infinity) * 0.07 } function withinBand(usage, bottom, top){ return usage > bottom ? Math.min(usage, top) - bottom : 0 } -
移除标记参数
标记参数有时候会让人难以理解那些函数可以调用
function deliveryDate(anOrder, isRush){ if(isRush){ ... }else{ ... } }function rushDeliveryDate(anOrder){ ... } function regularDeliveryDate(anOrder){ ... }做法
- 针对参数的每一种可能值新建一个函数
- 对于字面量作为参数的函数调用者,改为调用新建的明确函数
function deliveryDate(anOrder, isRush){ if(isRush){ let deliveryTime if(['MA', 'CT'].includes(anOrder.deliveryState)) deliveryTime = 1 else if(['NY', 'NH'].includes(anOrder.deliveryState)) deliveryTime = 2 else deliveryTime = 3 return deliveryTime }else{ let deliveryTime if(['MA', 'CT', 'NY'].includes(anOrder.deliveryState)) deliveryTime = 2 else if(['ME', 'NH'].includes(anOrder.deliveryState)) deliveryTime = 3 else deliveryTime = 4 return deliveryTime } }function rushDeliveryDate(anOrder){ let deliveryTime if(['MA', 'CT'].includes(anOrder.deliveryState)) deliveryTime = 1 else if(['NY', 'NH'].includes(anOrder.deliveryState)) deliveryTime = 2 else deliveryTime = 3 return deliveryTime } function regularDeliveryDate(anOrder){ let deliveryTime if(['MA', 'CT', 'NY'].includes(anOrder.deliveryState)) deliveryTime = 2 else if(['ME', 'NH'].includes(anOrder.deliveryState)) deliveryTime = 3 else deliveryTime = 4 return deliveryTime } -
保持对象完整
把一个对象内的字段拆成几个变量,再传入函数中,不如保持完整传入函数中去解析
const low = dayTempRange.low const high = dayTempRange.high function withinRange(low, high){...}function withinRange(aNumberRange){...}做法
- 新建一个空函数,给它期望的参数列表
- 再新函数中调用旧函数,并将新参数映射到旧的参数列表中
- 逐一修改旧函数的调用者,令其使用新函数
- 使用内联函数将旧函数内联到新函数中,给新函数改名后,同时修改所有的引用点
const dayTempRange = { low: 10, high: 40 } const low = dayTempRange.low const high = dayTempRange.high if(withinRange(low, high)){ console.log('123') } function withinRange(low, high){ return low > 9 && 41 > high }if(withinRange(dayTempRange)){ console.log('123') } function withinRange(aNumberRange){ return aNumberRange.low > 9 && 41 > aNumberRange.high } -
以查询取代参数
频繁的传递参数会让函数看起来变得复杂
class Order { get finalPrice(){ return this.discountedPrice(basePrice, discount) } discountedPrice(price, discount){...} }class Order { get finalPrice(){ return this.discountedPrice() } get basePrice() {...} get discount() {...} discountedPrice(){...} }做法
- 使用提炼函数将参数的计算过程提炼到一个独立的函数中
- 将函数体内引用该参数的地方改为调用新建的函数
class Order { constructor(quantity, price){ this.quantity = quantity; this.price = price; } get finalPrice(){ const basePrice = this.price * this.quantity let discount if(this.quantity > 100) discount = 2 else discount = 1 return this.discountedPrice(basePrice, discount) } discountedPrice(price, discount){ switch(discount){ case 1: return price * 0.9 case 2: return price * 0.8 } } }class Order { constructor(quantity, price){ this.quantity = quantity; this.price = price; } get finalPrice(){ return this.discountedPrice() } get basePrice() { return this.price * this.quantity } get discount() { return this.quantity > 100 ? 2 : 1 } discountedPrice(){ switch(this.discount){ case 1: return this.basePrice * 0.9 case 2: return this.basePrice * 0.8 } } } -
以参数取代查询
当遇到引用复杂,需要调用者弄清参数意义时,需要用参数取代查询
const thermostat = {} class HeatingPlan { get targetTemperature(){ if(thermostat.t > this.max) return this.max else if(thermostat.t < this.min) return this.min return thermostat.t } }class HeatingPlan { targetTemperature(t){ if(t > this.max) return this.max else if(t < this.min) return this.min return t } }做法
- 对执行查询操作对代码提炼变量,将其从函数体中剥离出来
- 现有的函数体不再执行查询操作
const thermostat = { selectTemperature: 20 } function setToHeat(){ thermostat.selectTemperature += 10 } function setToCool(){ thermostat.selectTemperature -= 10 } class HeatingPlan { constructor(max, min){ this.max = max; this.min = min; } get targetTemperature(){ if(thermostat.selectTemperature > this.max) return this.max else if(thermostat.selectTemperature < this.min) return this.min return thermostat.selectTemperature } }function setToHeat(){ thermostat.selectTemperature += 10 } function setToCool(){ thermostat.selectTemperature -= 10 } class HeatingPlan { constructor(max, min){ this.max = max; this.min = min; } targetTemperature(selectTemperature){ if(selectTemperature > this.max) return this.max else if(selectTemperature < this.min) return this.min return selectTemperature } } -
移除设值函数
如果不可变的数据,不暴露修改的方法
class Person { get name(){return this._name } set name(arg){ return this._name = arg } }class Person { get name(){return this._name } }做法
- 使用私有字段的实现方式
- 移除设值函数
class Person { constructor(name){ this._name = name; } get name(){return this._name } set name(arg){ return this._name = arg } }class Person { constructor(name){ this._name = name; } get name(){return this._name } } -
以工厂函数取代构造函数
构造函数在部分普通场合难以适用时,可以将其改为工厂函数
class Employee{ constructor(name, typeCode){ this. _name = name this._typeCode = typeCode } }function createEngineer(name){ return new Employee(name, 'E') }做法
- 新建一个工厂函数,让它地道用现有的构造函数
- 把调用构造函数的方法改为调用工厂函数
- 尽量修改构造函数的可见范围
class Employee{ constructor(name, typeCode){ this. _name = name this._typeCode = typeCode } get name(){return this._name } get type(){return Employee.legalTypeCode[this._typeCode] } static get legalTypeCode(){ return { 'E': 'Engineer', 'M': 'Manager', 'S': 'Salesman' } } }class Employee{ constructor(name, typeCode){ this. _name = name this._typeCode = typeCode } get name(){return this._name } get type(){return Employee.legalTypeCode[this._typeCode] } static get legalTypeCode(){ return { 'E': 'Engineer', 'M': 'Manager', 'S': 'Salesman' } } } function createEngineer(name){ return new Employee(name, 'E') } -
以命令取代函数
当函数里拥有许多复杂操作时,可以改成命令对象模式去处理
function score() { ... }function score() { return new Score().execute() } class Scorer{ execute(){ this.scoreSmoking() this.stateWithLowCertification() } scoreSmoking(){} stateWithLowCertification(){} }做法
- 创建一个空类,包含其目标函数
- 给每个参数都创建一个字段
function score(candidate, medicalExam, scoringGuide) { let result = 0 let healthLevel = 0 let highMedicalRiskFlag = false if(medicalExam.isSmoker){ healthLevel += 10 highMedicalRiskFlag = true } let certificationGrade = 'regular' if(scoringGuide.stateWithLowCertification(candidate.originState)){ certificateGrade = 'low' result -= 5 } result -= Math.max(healthLevel - 5, 0) return result }function score(candidate, medicalExam, scoringGuide) { return new Score(candidate, medicalExam, scoringGuide).execute() } class Scorer{ constructor(candidate, medicalExam, scoringGuide){ this._candidate = candidate this._medicalExam = medicalExam this._scoringGuide = scoringGuide } execute(){ this._result = 0 this._healthLevel = 0 this._highMedicalRiskFlag = false this.scoreSmoking() this.stateWithLowCertification() this._result -= Math.max(this._healthLevel - 5, 0) return this._result } scoreSmoking(){ if(this._medicalExam.isSmoker){ this._healthLevel += 10 this._highMedicalRiskFlag = true } } stateWithLowCertification(){ this._certificationGrade = 'regular' if(this._scoringGuide.stateWithLowCertification(this._candidate.originState)){ this._certificationGrade = 'low' this._result -= 5 } } } -
以函数取代命令
使用函数去完成比较简单的任务
class ChargeCalculator{ get basePrice(){} get charge(){} } function charge(){ return new ChargeCalculator().charge }function charge(){ ... }做法
- 对命令对象在执行阶段用到的函数使用内联函数
- 将构造函数中的参数转移到执行函数
- 对所有的字段在执行函数中找到引用的地方,并改为参数
class ChargeCalculator{ constructor(customer, usage, provider){ this.customer = customer this.usage = usage this.provider = provider } get basePrice(){ return this.customer.baseRate * this.usage } get charge(){ return this.basePrice + this.provider.connectionCharge } } function charge(customer, usage, provider){ return new ChargeCalculator(customer, usage, provider).charge }function charge(customer, usage, provider){ const basePrice = customer.baseRate * usage return basePrice + provider.connectionCharge }
处理继承关系
-
函数上移
如果某个函数在各个子类的函数体中相同,则将函数上移
class Party { } class Employee extends Party { annualCost(){ ... } } class Department extends Party { annualCost(){ ... } }class Party { annualCost(){ ... } } class Employee extends Party { } class Department extends Party { }做法
- 检查待提升函数,确定完全一致
- 检查函数体内引用的所有函数调用和字段都能从超类调用
- 如果待提升签名不同,则都需要修改成超类的字段名
- 在超类中新建一个函数,将某个待提升函数的代码复制到超类中
- 移除待提升的函数
class Party { constructor(monthlyCost){ this.monthlyCost = monthlyCost } } class Employee extends Party { get annualCost(){ return this.monthlyCost * 12 } } class Department extends Party { get totalAnnualCost(){ return this.monthlyCost * 12 } }class Party { constructor(monthlyCost){ this.monthlyCost = monthlyCost } get annualCost(){ return this.monthlyCost * 12 } } class Employee extends Party { } class Department extends Party { } -
字段上移
如果字段在各个子类中,字段可提升到超类
class Party { } class Employee extends Party { get annualCost(){ ... } } class Department extends Party { get annualCost(){ ... } }class Party { get annualCost(){ ... } } class Employee extends Party { } class Department extends Party { }做法
- 针对待提升的字段,检查他们的所有使用点,确定以同样的方式使用
- 如果字段名称不同,则先用变量改名
- 在超类中新增字段
class Party { constructor(monthlyCost){ this.monthlyCost = monthlyCost } } class Employee extends Party { get annualCost(){ return this.monthlyCost * 12 } } class Department extends Party { get totalAnnualCost(){ return this.monthlyCost * 12 } }class Party { constructor(monthlyCost){ this.monthlyCost = monthlyCost } get annualCost(){ return this.monthlyCost * 12 } } class Employee extends Party { } class Department extends Party { } -
构造函数本体上移
子类中存在共同实例属性,则可以提升到超类
class Party { } class Employee extends Party { constructor(){ super() this.name = name } } class Department extends Party { constructor(){ super() this.name = name } }class Party { constructor(name){ this.name = name } } class Employee extends Party { } class Department extends Party { }做法
- 如果超类不存在构造函数,直接定义一个。确保子类调用超类到构造函数
- 使用移动语句将子类中构造函数中的公共语句,移动到超类到构造函数调用的语句
- 逐一移除子类间的公共代码,将其提升到超类的构造函数中
class Party { } class Employee extends Party { constructor(name, id, monthlyCost){ super() this.name = name this.id = id this.monthlyCost = monthlyCost } } class Department extends Party { constructor(name, staff){ super() this.name = name this.staff = staff } }class Party { constructor(name){ this.name = name } } class Employee extends Party { constructor(name, id, monthlyCost){ super(name) this.id = id this.monthlyCost = monthlyCost } } class Department extends Party { constructor(name, staff){ super(name) this.staff = staff } } -
函数下移
如果某个函数只一个或者几个子类调用,则将函数下移到子类中
class Party { annualCost(){...} } class Employee extends Party {} class Department extends Party {}class Party {} class Employee extends Party { annualCost(){...} } class Department extends Party {}做法
- 将超类的函数移动到目标子类中
class Party { constructor(monthlyCost){ this.monthlyCost = monthlyCost } annualCost(){ return this.monthlyCost * 12 } } class Employee extends Party { get cost(){ return this.annualCost() * 10 } } class Department extends Party { }class Party { constructor(monthlyCost){ this.monthlyCost = monthlyCost } } class Employee extends Party { annualCost(){ return this.monthlyCost * 12 } get cost(){ return this.annualCost() * 10 } } class Department extends Party { } -
字段下移
如果某个字段只被一个子类用到,就将其搬移到需要该字段到子类中
class Party { get annualCost(){...} } class Employee extends Party {} class Department extends Party {}class Party {} class Employee extends Party { get annualCost(){...} } class Department extends Party {}做法
- 将超类中到字段移动到目标子类
class Party { constructor(monthlyCost){ this.monthlyCost = monthlyCost } get annualCost(){ return this.monthlyCost * 12 } } class Employee extends Party { get cost(){ return this.annualCost() * 10 } } class Department extends Party { }class Party { constructor(monthlyCost){ this.monthlyCost = monthlyCost } get annualCost(){ return this.monthlyCost * 12 } } class Employee extends Party { get cost(){ return this.annualCost * 10 } } class Department extends Party { } -
以子类取代类型码
用多种子类替代类型码判断
class Employee { validateType(type){...} }class Employee {} class Engineer extends Employee{ get type(){} } class Salesman extends Employee{ get type(){} } function createEmployee(name, type){ switch(type){ case 'engineer': return new Engineer(name) case 'salesman': return new Salesman(name) } }做法
- 封装类型码字段
- 任选一个类型码取值,创建一个子类,复写类型码类的取值函数,令其返回类型码的字面量
- 创建一个选择器的逻辑,把类型码参数映射到新的子类
- 正对每个类型码都重复创建子类
class Employee { constructor(name, type){ this.validateType(type) this._name = name this._type = type } validateType(type){ if(!['engineer', 'salesman', 'manager'].includes(type)){ throw new Error('Invalid type') } } toString() { return `${this._name} (${this._type})`} }class Employee { constructor(name){ this._name = name } toString() { return `${this._name} (${this.type})`} } class Engineer extends Employee{ get type(){ return 'engineer' } } class Salesman extends Employee{ get type(){ return 'salesman' } } class Manager extends Employee{ get type(){ return 'manager' } } function createEmployee(name, type){ switch(type){ case 'engineer': return new Engineer(name) case 'salesman': return new Salesman(name) case 'manager': return new Manager(name) default: throw new Error('Invalid type') } } -
移除子类
如果子类的用处太小,则可以移除子类,替换成超类的一个字段
class Person{ get genderCode(){} } class Male extends Person{ get genderCode(){} } class Female extends Person{ get genderCode(){} } function isMale(aPerson){ return aPerson instanceof Male}class Person{ constructor(){ this.genderCode = genderCode || 'X' } get isMale(){ ... } }做法
- 把子类的构造函数包装到超类的工厂中
- 新建一个字段用于代表子类的类型
- 将原来针对子类的判断函数修改未使用新建类字段
class Person{ constructor(name){ this.name = name } get genderCode(){ return 'X' } } class Male extends Person{ get genderCode(){ return 'X' } } class Female extends Person{ get genderCode(){ return 'F' } } function isMale(aPerson){ return aPerson instanceof Male}class Person{ constructor(name, genderCode){ this.name = name this.genderCode = genderCode || 'X' } get isMale(){ return this.genderCode === 'X' } } -
提炼超类
如果两个类在做相似的事情,可以继承机制把他们的相似之处提炼到超类中
class Employee {} class Department {}class Party{ constructor(name){ this.name = name } } class Employee extends Party{} class Department extends Party{}做法
- 为原本的类新建一个超类
- 把共同元素梳理出来移动到超类中
class Employee { constructor(name, id, monthlyCost){ this.name = name this.id = id this.monthlyCost = monthlyCost } get annualCost(){ return this.monthlyCost * 12 } } class Department { constructor(name, staff){ this.name = name this.staff = staff } get totalMonthlyCost(){ return this.staff.map(e => e.monthlyCost).reduce((prev, cur) => prev + cur, 0) } get totalAnnualCost(){ return this.totalMonthlyCost * 12 } }class Party{ constructor(name){ this.name = name } get annualCost(){ return this.monthlyCost * 12 } } class Employee extends Party{ constructor(name, id, monthlyCost){ super(name) this.id = id this.monthlyCost = monthlyCost } } class Department extends Party{ constructor(name, staff){ super(name) this.staff = staff } get monthlyCost(){ return this.staff.map(e => e.monthlyCost).reduce((prev, cur) => prev + cur, 0) } } -
折叠继承体系
当子类与父类没多大差别时,直接折叠子类与超类
做法
- 选择要移除的类
- 将其移动到目标类中
-
以委托取代子类
继承存在局限性,应该用组合与继承相结合的方式去改造
class Booking{} class PremiumBooking extends Booking{}class Booking{ _bePremium(){ this._premium = new PremiumBookingDelegate(this, extra) } } class PremiumBookingDelegate{} function createPremiumBooking(extra){ booking._bePremium(extra) }做法
- 如果构造函数由多个调用者,先用工厂函数包裹起来
- 创建一个空的委托累,这个类的构造函数应该接受所有子类特有的数据项,并以参数的形式接受一个指回超类的引用
- 在超类中添加一个字段,用于安放委托对象
- 修改子类的创建逻辑,使其初始化上述的委托字段,放入一个委托对象实例
- 选择一个子类的函数,使用搬移函数移动函数放入委托类
- 如果被搬移函数还在子类之外被调用,就把留在源类中的委托代码从子类移到超类,并委托代码前加上卫语句,检查委托对象存在。
- 如果没有,则直接移除代码,重复上述过程,直到所有函数都移动到委托类
class Booking{ constructor(show, date){ this.show = show this.date = date } get hasTalkBack(){ return this.show.talkBack && !this.isPeakDay } get basePrice(){ let result = this.show.price if(this.isPeakDay) result *= 1.15 return result } } class PremiumBooking extends Booking{ constructor(show, date, extra){ super(show, date) this.extra = extra } get hasTalkBack(){ return this.show.talkBack } get basePrice(){ return super.basePrice * this.extra.fee } get hasDinner(){ return this.extra.dinner && !this.isPeakDay } }class Booking{ constructor(show, date){ this.show = show this.date = date } get hasTalkBack(){ return this._premiumDelegate ? this._premiumDelegate.hasTalkBack : this.show.talkBack && !this.isPeakDay } get basePrice(){ let result = this.show.price if(this.isPeakDay) result *= 1.15 return this._premiumDelegate ? this._premiumDelegate.extendBasePrice(result) : result } get hasDinner(){ return this._premiumDelegate ? this._premiumDelegate.hasDinner : false } _bePremium(extra){ this._premiumDelegate = new PremiumBookingDelegate(this, extra) } } class PremiumBookingDelegate{ constructor(hostBooking, extra){ this.host = hostBooking this.extra = extra } get hasTalkBack(){ return this.host.show.talkBack } get hasDinner(){ return this.extra.dinner && !this.host.isPeakDay } extendBasePrice(base){ return base * this.extra.fee } } function createBooking(show, date){ return new Booking(show, date) } function createPremiumBooking(show, date, extra){ let result = new Booking(show, date) result._bePremium(extra) return result } -
以委托取代超类
优先使用继承,当继承有问题,使用委托取代超类
class CatalogItem{...} class Scroll extends CatalogItem{...}class CatalogItem{...} class Scroll{ constructor(catalogId, catalog){ this.catalogItem = catalog.get(catalogId) // new CatalogItem() } }做法
- 在子类中新建一个字段,使其引用超类的一个对象,并将这个委托引用初始化为超类的实例
- 针对超类每个函数,在子类中创建一个转发函数,将请求转发给委托引用
- 当所有的超类函数都被转发函数覆盖后,去除继承关系
class CatalogItem{ constructor(id, title, tags){ this.id = id this.title = title this.tags = tags } hasTag(arg){ return this.tags.includes(arg) } } class Scroll extends CatalogItem{ constructor(id, title, tags, dateLastCleaned){ super(id, title, tags) this.lastCleaned = dateLastCleaned } needsCleaning(targetDate){ const threshold = this.hasTag('revered') ? 700 : 1500 return this.daysSinceLastCleaning(targetDate) > threshold } daysSinceLastCleaning(targetDate){ return this.lastCleaned.until(targetDate) } }class CatalogItem{ constructor(id, title, tags){ this.id = id this.title = title this.tags = tags } hasTag(arg){ return this.tags.includes(arg) } } class Scroll{ constructor(id, dateLastCleaned, catalogId, catalog){ this.id = id this.catalogItem = catalog.get(catalogId) this.lastCleaned = dateLastCleaned } get id(){return this.catalogItem.id} get title(){return this.catalogItem.title} get tags(){return this.catalogItem.tags} hasTag(arg){ return this.catalogItem.hasTag(arg) } needsCleaning(targetDate){ const threshold = this.hasTag('revered') ? 700 : 1500 return this.daysSinceLastCleaning(targetDate) > threshold } daysSinceLastCleaning(targetDate){ return this.lastCleaned.until(targetDate) } }