第7章 封装
7.1 封装记录(Encapsulate Record)
organization = {
name: "Acme Gooseberries", country: "GB"};
–>
class Organization {
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;}
}
对于可变数据,我总是更偏爱使用类对象而非记录。对象可以隐藏结构的细节,仅为这3个值提供对应的方法。该对象的用户不必追究存储的细节和计算的过程。同时,这种封装还有助于字段的改名:我可以重新命名字段,但同时提供新老字段名的访问方法,这样我就可以渐进地修改调用方,直到替换全部完成。
7.2 封装集合(Encapsulate Collection)
class Person {
get courses() {
return this._courses;}
set courses(aList) {
this._courses = aList;}
}
–>
class Person {
get courses() {
return this._courses.slice();}
addCourse(aCourse) {
... }
removeCourse(aCourse) {
... }
我喜欢封装程序中的所有可变数据。这使我很容易看清楚数据被修改的地点和修改方式,这样当我需要更改数据结构时就非常方便。我们通常鼓励封装——使用面向对象技术的开发者对封装尤为重视——但封装集合时人们常常犯一个错误:只对集合变量的访问进行了封装,但依然让取值函数返回集合本身。
为避免此种情况,我会在类上提供一些修改集合的方法——通常是“添加”和“移除”方法。这样就可使对集合的修改必须经过类,当程序演化变大时,我依然能轻易找出修改点。
7.3 以对象取代基本类型(Replace Primitive with Object)
orders.filter(o => "high" === o.priority || "rush" === o.priority);
–>
orders.filter(o => o.priority.higherThan(new Priority("normal")))
一旦我发现对某个数据的操作不仅仅局限于打印时,我就会为它创建一个新类。一开始这个类也许只是简单包装一下简单类型的数据,不过只要类有了,日后添加的业务逻辑就有地可去了。
7.4 以查询取代临时变量(Replace Temp with Query)
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 (this.basePrice > 1000)
return this.basePrice * 0.95;
else
return this.basePrice * 0.98;
如果我正在分解一个冗长的函数,那么将变量抽取到函数里能使函数的分解过程更简单,因为我就不再需要将变量作为参数传递给提炼出来的小函数。将变量的计算逻辑放到函数中,也有助于在提炼得到的函数与原函数之间设立清晰的边界,这能帮我发现并避免难缠的依赖及副作用。
7.5 提炼类(Extract Class)
反向重构:内联类(186)
class Person {
get officeAreaCode() {
return this._officeAreaCode;}
get officeNumber() {
return this._officeNumber;}
–>
class Person {
get officeAreaCode() {
return this._telephoneNumber.areaCode;