封装

以下内容来自《重构》第二版第七章部分内容

1. 封装记录

organization = { name: "xp", country: "CH" };

// 封装

class Origanization {
  constructor(data) {
    this._name = data.name;
    this._country = data.country;
  }
  get name() {
    return this._name;
  }
  set name(arg) {
    this._name = arg;
  }
  get country() {
    return this._country;
  }
  set country(arg) {
    this._country = arg;
  }
}

记录型结构是多树编程语言提供的一种常见特性。它们能直观地组织起存在关联的数据,让我们可以将数据作为有意义的单元传递,而不仅是一堆数据的拼凑。单简单的记录型结构也有缺陷,最恼人的一点是:它强迫我们清晰的区分 ”记录中存储的数据“ 和 ”通过计算得到的数据“。 假使我们要描述一个整数闭区间,我们可以使用 { start: 1, end: 5 } 描述,或者使用 { start: 1, length: 5 }

  • 对于可变数据我们可以使用对象而非记录
    • 对象可以隐藏结构的细节,直接为变量的值提供方法。用户不必追究存储的细节和计算的过程

示例:

首先从常量开始

organization = { name: "xp", country: "CH" };

下面是对这个常量读取和更新的地方

result += `<h1>${organization.name}</h1>`
organization.name = newName

重构的第一步很简单,先使用 封装变量

function getRawDataOfOranization(){ return organization; }

读取的例子

result += `<h1>${getRawDataOfOranization().name}</h1>`;

更新的例子

getRawDataOfOranization().name = newName

这里使用的不全是标准的 封装变量 手法。封装记录意味着,仅仅替换变量还不够,我们还需要控制它的使用方式。可以使用类来替换记录,从而达到这一目的

class Organization {
  constructor(data) {
    this._data = data
  }
}

顶层作用域:

const organization = new Organization({ name: "xp", country: "CH" })

function getRawDataOfOranization() { return organization._data }
function getOrganization() { return organization }

创建完对象后,开始寻找该记录的使用点,并更新

// class Organization
set name(aString) { this._name = aString }

// 客户端
getOrganization().name = newName

同样的,将所有读取记录的地方用取值函数来替代

// class Organization
get name() { return this._name }

// 客户端
result += `<h1>${getOrganization().name}</h1>`

完成引用替换后

修改

function getRawDataOfOranization(){ return organization; }

function getOrganization() { return organization }

最后,将类改成

class Organization {
  constructor(data) {
    this._name = data.name
    this._country = data.country
  }
  get name() { return this._name }
  set name(aString) { return this._name = aString }
  get country() { return this._country }
  set country(aCountryCode) { return this._country = aCountryCode }
}

2. 以查询取代临时变量

const basePrice = this._quantity * this._itemPrice
if (basePrice > 1000)
  return basePrice * 0.95
else
  return basePrice * 0.98

// ======== 重构 ========

get basePrice() { this._quantity * this._itemPrice}
if (basePrice > 1000)
  return this.basePrice * 0.95
else
  return this.basePrice * 0.98

如果在不同的地方看到同一段变量的计算逻辑,可以把他们挪到同一个函数里

以查询取代临时变量 手法只适用于处理某些类型的临时变量:那些只被计算一次且之后不再被修改的变量。最简单的情况是这个临时变量只被赋值一次,但在更复杂的代码片段里,变量也可以被多次赋值—此时应该将这些计算代码一并提炼到查询函数中。并且待提炼

的逻辑多次计算同样的变量时,应该能得到相同的结果。因此,对于那些做快照用途的临时变量,就不能使用本手法

  • 检查变量在使用前是否已经完全计算完毕,检查计算它的那段代码是否每次都能得到一样的值
  • 如果变量目前不是只读的,但是可以改造成只读变量,那就先改造它
  • 测试
  • 将变量赋值的代码提炼成函数
    • 如果变量和函数不能使用同样的名字,那么先为函数取个临时的名字
    • 确保待提炼函数没有副作用。若有,应先用 查询函数和修改函数分离 手法隔离副作用
  • 测试
  • 应用 内联变量 手法移除临时变量

示例:简单的订单类

class Order{
  constructor(quantity, item) {
    this._quantity = quantity
    this._item = item
  }
  get price() {
    var basePrice = this._quantity * this._item.price
    var discountFactor = 0.98
    if (basePrice > 1000) discountFactor -= 0.03
    return basePrice * discountFactor
  }
}

这里把 basePricediscountFactor 两个临时变量提炼成函数

先从 basePrice 开始

class Order{
  constructor(quantity, item) {
    this._quantity = quantity
    this._item = item
  }
  get price() {
    const basePrice = this.basePrice
    var discountFactor = 0.98
    if (basePrice > 1000) discountFactor -= 0.03
    return basePrice * discountFactor
  }

  // 提炼函数
  get basePrice() {
    return this._quantity * this._item.price
  }
}

使用 内联变量

class Order{
  constructor(quantity, item) {
    this._quantity = quantity
    this._item = item
  }
  get price() {
    var discountFactor = 0.98
    if (this.basePrice > 1000) discountFactor -= 0.03
    return this.basePrice * discountFactor
  }

  get basePrice() {
    return this._quantity * this._item.price
  }
}

接下来对 discountFactor 进行同样的步骤

class Order{
  constructor(quantity, item) {
    this._quantity = quantity
    this._item = item
  }
  get price() {
    return this.basePrice * this.discountFactor
  }

  get basePrice() {
    return this._quantity * this._item.price
  }

  get discountFactor() {
    var discountFactor = 0.98
    if (this.basePrice > 1000) discountFactor -= 0.03
    return discountFactor
  }
}

3. 提炼类

class Person {
  get officeAreaCode() { return this._officeAreaCode }
  get officeNumber() { return this.officeNumber }
}
=============== 重构 ===============
class Person {
  get officeAreaCode() { return this._telephoneNumber.areaCode }
  get officeNumber() { return this._telephoneNumber.number }
}

class TelephoneNumber {
  get areaCode() { return this._areaCode }
  get number() { return this._number }
}

	一个类应该是一个清晰的抽象,只处理一些功能明确的责任。但是实际工作中,类会不断成长扩展,到了后面类就会变得过分复杂。如果某些数据和函数总是一起出现,某些数据经常同时变化甚至彼此相依,这就表示你应该将它们分离出去。
  • 决定如何分解类所负责的责任
  • 创建一个新的类,用以表现从旧类中分离出来的责任
    • 如果旧类剩下的责任与旧类的名称不符合,为旧类改名
  • 构造旧类时创建一个新类的实例,建立“从旧类访问新类”的连接关系
  • 对于你想搬移的每一个字段,运用 搬移字段 。每次更改后运行测试
  • 使用 搬移函数 将必要的函数搬移到新类。先搬移较低层函数(也就是“被其它函数调用” 多于 “调用其它函数”者)。每次更改之后运行测试
  • 检查两个类的接口,去掉不再需要的函数,必要时为函数重新取一个适合新环境的名字
  • 决定是否公开信的类。如果确实需要,考虑对新使用 将引用对象改为值对象 使其成为一值对象

首先从一个Person 类开始

class Person {
  get name() { return this._name }
  set name(arg) { this._name = arg }
  get telephoneNumber() { return `(${this.officeAreaCode}) ${this.officeNumber}` }
  get officeAreaCode() { return this._officeAreaCode }
  set officeAreaCode(arg) { this._officeAreaCode = arg}
  get officeNumber() { return this._officeNumber }
  set officeNumber(arg) { this._officeNumber = arg}
}

这里可以将与电话号码相关的行为分离到一个独立的类中。

class TelephoneNumber{
 
}

然后在构造Person 类时创建 TelephoneNumber类的一个实例

class Person{
  constructor() {
    this._telephoneNumber = new TelephoneNumber()
  }
}
class TelephoneNumber {
  get officeAreaCode() { return this._officeAreaCode }
  set officeAreaCode(arg) { this._officeAreaCode = arg }
}

现在使用***搬移字段*** 搬移一个字段

class Person {
  get officeAreaCode() { return this._telephoneNumber.officeAreaCode }
  set officeAreaCode(arg) { this._telephoneNumber.officeAreaCode = arg }
}

再次运行测试,对下一个字段进行处理

class TelephoneNumber {
  get officeNumber() { return this._officeNumber }
  set officeNumber(arg) { this._officeNumber = arg }
}

class Person {
  get officeNumber() { return this._telephoneNumber.officeNumber }
  set officeNumber(arg) { this._telephoneNumber.officeNumber = arg}
}

再次测试,搬移对电话号码的取值函数

class Person {
  get telephoneNumber() { return  this._telephoneNumber.telephoneNumber }
}

class TelephoneNumber {
  get telephoneNumber() { return `(${this.officeAreaCode}) ${this.officeNumber}` }
}

​ 很显然“电话号码”不应该拥有“办公室”的概念,所以重命名一下变量

class Person {
  constructor() {
    this._telephoneNumber = new TelephoneNumber()
  }
  get name() { return this._name }
  set name(arg) { this._name = arg }
  get officeAreaCode() { return this._telephoneNumber.areaCode }
  set officeAreaCode(arg) { this._telephoneNumber.areaCode = arg }
  get officeNumber() { return this._telephoneNumber.number }
  set officeNumber(arg) { this._telephoneNumber.number = arg }
}

class TelephoneNumber {
  get areaCode() { return this._areaCode }
  set areaCode(arg) { this._areaCode = arg }
  get number() { return this._number }
  set number(arg) { this._number = arg }
  get toString() { return `(${this.officeAreaCode}) ${this.officeNumber}` }
}

同时 TelephoneNumber 类上有一个对自己 (telephone number)取值也没什么道理,这里使用***函数改名***

class TelephoneNumber {
  get toString() { return `(${this.officeAreaCode}) ${this.officeNumber}` }
}

class Person {
  get telePhoneNumber() { return this._telePhone.toString() }
}

4. 内联类

​ 内联类与 提炼类 刚好相反。如果一个类不再承担足够的责任,不再有单独存在的理由(这通常是因为此前的重构动作移走了这个类的责任),这时候挑选这一“萎缩类”的最频繁用户,以本手法将“萎缩类”塞进另一个类中。

  • 对于待内联类(源类)中所有的public函数,在目标类上创建一个对应的函数,新创建的所有函数应该直接委托至源类
  • 修改源类public方法所有的引用点,令它们调用目标类对应的委托方法。每次修改后运行测试
  • 将源类中的函数与数据全部搬移到目标类,每次修改之后进行测试,直到源类变成空壳为止
  • 删除源类

例子:下面这个类存储了一次物流运输(shipment)的若干信息(tracking information)

class TrackingInformation {
  get shippingCompany() { return this._shippingCompany }
  set shippingCompany(arg) { this._shippingCompany = arg }
  get trackingNumber() { return this._trackingNumber }
  set trackingNumber(arg) { this._trackingNumber = arg }
  get display() {
    return `${this.shippingCompany}: ${this.trackingNumber}`
  }
}

它作为Shipment(物流)类的一部分被使用

class Shipment {
  get trackingInfo() {
    return this._trackingInformation.display
  }
  get trackingInformation() { return this._trackingInformation }
  set trackingInformation(aTrackingInformation) { 
    this._trackingInformation = aTrackingInformation
  }
}

TrackingInformation 类过去可能有很多的职责,但是现在已经不能肩负起它的责任,所以要把它内联到Shipment类中

​ 首先需要寻找 TranckingInformation 类的方法有哪些调用点

调用方:

aShipment.trackingInformation.shippingCompany = request.vendor

首先在 Shipment 类中创建一个委托方法,并调整客户端代码,使其调用这个委托方法

class Shipment {
  set shippingCompany(arg) { this._trackingInformation.shippingCompany = arg }
}

调用方:

aShipment.shippingCompany = request.vendor

​ 对于 TrackingInformation 类中所有为客户端调用的方法,使用上面相同的手法。之后就可以将源类中的所有东西都搬移到 Shipment

​ 先对 display 使用 内联函数

class Shipment {
  get trackingInfo() {
    return `${this._shippingCompany}: ${this._trackNumber}`
  }
}

继续搬移 “收货公司” (shopping company)字段

get shippingCompany() { return this._shippingCompany }
set shippingCompany(arg) { this._shippingCompany = arg }

搬移完所有函数,就可以删除 TrackingInformation 类。

class Shipment {
  get trackingInfo() {
    return `${this.shippingCompany}: ${this.trackingNumber}`
  }
  get shippingCompany() { return this._shippingCompany }
  set shippingCompany(arg) { this._shippingCompany = arg }
  get trackingNumber() { return this._trackingNumber }
  set trackingNumber(aTrackingNumber) { 
    this._trackingNumber = aTrackingNumber
  }
}

5. 隐藏委托关系

manager = aPerson.department.manager

===== 重构 =====

manager = aPerson.manager

class Person {
  get manager() { return this.department.manager }
}

​ 一个好的模块设计,“封装”即使不是其最关键特征,也是关键特征之一。封装 意味着每个模块都应该尽可能少了解系统的其它部分。如此一来,一旦发生变化,需要了解这一变化的模块就会比较少----这会使变化比较容易进行。

​ 当初我们学面向对象技术的时候就被教导,封装意味着应该隐藏自己的字段。随着经验日渐丰富,你会发现,有更多可以(而且值得)封装的东西。

​ 如果某些客户端先通过服务对象的一个字段得到另一个对象(受托类),然后调用后者的函数,那么客户端就必须知晓这一层委托关系。万一受委托类修改了接口,变化会波及通过服务对象使用它的所有客户端。我们可以在服务对象上放置一个简单的委托函数,将委托关系隐藏起来,从而去除这种依赖。这么一来,即使将来委托关系发生变化,变化也只会影响服务对象,而不会直接波及所有客户端。

  • 对于每个委托关系中的函数,在服务对象端建立一个简单的委托函数
  • 调整客户端,令它只调用服务对象提供的函数。每次调用后运行测试
  • 如果将来不再有任何客户端需要取用Delegate 受托类,便可以移除服务对象中的相关访问函数
  • 测试

示例:本例从两个类开始,代表“人”的 Person 和代表 “部门” 的 Department

class Person {
  constructor(name) {
    this._name = name
  }
  get name() { return this._name }
  get department() { return this._department }
  set department(arg) { this._department = arg }
}

class Department {
  get chargeCode() { return this.chargeCode }
  set chargeCode(arg) { this.chargeCode = arg }
  get manager() { return this._manager }
  set manager(arg) { this._manager = arg }
}

有些客户端希望知道某人的经理是谁,为此,他必须先取得 Department 对象

客户端代码

mamager = aPerson.department.manage

​ 这样的编码就对客户端揭露了 Department 的工作原理,于是客户知道: Department 负责追踪 “经理” 的信息。如果对客户隐藏 Department,可以减少耦合。为了实现这一目的,在 Person 中建立一个简单的委托函数

class Person {
  get manager() { return this._department.manager }
}

客户端代码:

manager = aPerson.manager

只要完成了对 Department 所有函数的修改,并相应修改了 Person 的所有客户端,我就可以移除 Person 中的 department 访问函数了

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值