昨天我们发了第一篇,今天让我们来继续第二天。先来回顾一下昨天我们都实现了哪些:
- Add Parameter(添加参数)
- Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)
- Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)

(图片:贡嘎山)
什么是重构 ?
简单理解就是不改变软件可观察行为的前提下,改善其内部结构,以提高理解性和降低修改成本。
1. 这是如下我们要实现的目标任务列表(每天进步一点点⏰)
- Change Reference to Value(将引用对象改为值对象)
- Change Value to Reference(将值对象改为引用对象)
- Collapse Hierarchy(折叠继承体系)
- Consolidate Conditional Expression(合并条件表达式)
- Consolidate Duplicate Conditional Fragments(合并重复的条件片段)
- Convert Procedural Design to Objects(将过程化设计转化为对象设计)
- Decompose Conditional(分解条件表达式)
- Duplicate Observed Data(复制“被监视数据”)
- Encapsulate Collection(封装集合)
- Encapsulate Downcast(封装向下转型)
- Encapsulate Field(封装字段)
- Extract Class(提炼类)
- Extract Hierarchy(提炼继承体系)
- Extract Interface(提炼接口)
- Extract Method(提炼函数)
- Extract Subclass(提炼子类)
- Extract Superclass(提炼超类)
- Form Template Method(塑造模板函数)
- Hide Delegate(隐藏“委托关系”)
- Hide Method(隐藏函数)
- Inline Class(将类内联化)
- Inline Method(内联函数)
- Inline Temp(内联临时变量)
- Introduce Assertion(引入断言)
- Introduce Explaining Variable(引入解释性变量)
- Introduce Foreign Method(引入外加函数)
- Introduce Local Extension(引入本地扩展)
- Introduce Null Object(引入Null对象)
- Introduce Parameter Object(引入参数对象)
- Move Field(搬移字段)
- Move Method(搬移函数)
- Parameterize Method(令函数携带参数)
- Preserve Whole Object(保持对象完整)
- Pull Up Constructor Body(构造函数本体上移)
- Pull Up Field(字段上移)
- Pull Up Method(函数上移)
- Push Down Field(字段下移)
- Push Down Method(函数下移)
- Remove Assignments to Parameters(移除对参数的赋值)
- Remove Control Flag(移除控制标记)
- Remove Middle Man(移除中间人)
- Remove Parameter(移除参数)
- Remove Setting Method(移除设值函数)
- Rename Method(函数改名)
- Replace Array with Object(以对象取代数组)
- Replace Conditional with Polymorphism(以多态取代条件表达式)
- Replace Constructor with Factory Method(以工厂函数取代构造函数)
- Replace Data Value with Object(以对象取代数据值)
- Replace Delegation with Inheritance(以继承取代委托)
- Replace Error Code with Exception(以异常取代错误码)
- Replace Exception with Test(以测试取代异常)
- Replace Inheritance with Delegation(以委托取代继承)
- Replace Magic Number with Symbolic Constant(以字面常量取代魔法数)
- Replace Method with Method Object(以函数对象取代函数)
- Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式)
- Replace Parameter with Explicit Methods(以明确函数取代参数)
- Replace Parameter with Methods(以函数取代参数)
- Replace Record with Data Class(以数据类取代记录)
- Replace Subclass with Fields(以字段取代子类)
- Replace Temp with Query(以查询取代临时变量)
- Replace Type Code with Class(以类取代类型码)
- Replace Type Code with State/Strategy(以State/Strategy取代类型码)
- Replace Type Code with Subclasses(以子类取代类型码)
- Self Encapsulate Field(自封装字段)
- Separate Domain from Presentation(将领域和表述/显示分离)
- Separate Query from Modifier(将查询函数和修改函数分离)
- Split Temporary Variable(分解临时变量)
- Substitute Algorithm(替换算法)
- Tease Apart Inheritance(梳理并分解继承体系)
2. Change Reference to Value(将引用对象改为值对象)
描述🍏:你有一个引用对象,很小且不可改变,而且不易管理。
动机🍃: 在分布系统和并发系统中,不可变的值对象特别有用,因为你无需考虑他们的同步问题。
export interface ICurrency {
getCode(): string;
}
class Currency implements ICurrency {
private _code: string;
constructor(code: string) {
this._code = code;
}
public getCode(): string {
return this._code;
}
}
//假设现在你上个月的工资发的是人民币 类里包含了其他汇率等
const rmb = new Currency('RMB');
//这个月也发的是人民币
const rmb2 = new Currency('RMB');
//都是人民币这个结果显然是false
console.log(rmb == rmb2);
要把一个引用对象变成值对象,关键动作:检查它是否不可变。如果不是,我们就用不到使用本项重构,因为可变的值会造成凡人的别名问题。
//如上RMB显然是不可变的币种 所以我们可以把它改成值类型
//有2种方式可以改变他为值类型 一种是设计模式里的单例
// 我们这里按照原书的逻辑添加了一个equal的函数
export interface ICurrency {
equals(arg: Object): boolean;
getCode(): string;
}
class Currency implements ICurrency {
...
public equals(arg: Object): boolean {
if (!(arg instanceof Currency)) {
return false;
}
return this._code === arg._code;
}
...
}
const rmb = new Currency('RMB');
const rmb2 = new Currency('RMB');
console.log(rmb.equals(rmb2));
因为两个对象实际上是对值的比较了 无论你声明多少个,我们实际都操作rmb这个实例了。
接下来,我就要多说几句了,实际上这个规则对我们前端用途非常的大。Redux.js和 Immutable.js
yarn add immutable -D
yarn add redux -D
yarn add @types/immutable -D
// map1是不可变对象 正用了这条重构规则
import { Map } from 'immutable';
const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50);
console.log(map1.get('b') + ' vs. ' + map2.get('b'));
// 完整版的code地址
// https://redux.js.org/recipes/usage-with-typescript
import { Action } from 'redux'
import { sendMessage } from './store/chat/actions'
import { AppState } from './store'
import { ThunkAction } from 'redux-thunk'
export const thunkSendMessage = (
message: string
): ThunkAction<void, AppState, null, Action<string>> => async dispatch => {
const asyncResp = await exampleAPI()
dispatch(
sendMessage({
message,
user: asyncResp,
timestamp: new Date().getTime()
})
)
}
function exampleAPI() {
return Promise.resolve('Async Chat Bot')
}
Redux的状态肯定是不可变的,否则整个对象的内存地址不变就无法响应我们的React组件了。
3. Change Value to Reference(将值对象改为引用对象)
描述🐥:从一个类衍生出许多彼此相等的实例,希望将他们替换为同一对象。
动机🐈:许多系统中,像日期、数量、名称等他们完全由所含的意义来标识,你并不在乎他们有多少副本。如果在你的副本中需要某一对象的变化影响到引用了他的地方,就需要考虑将这个对象变成一个引用对象。
class Order {
public setCustomer(customerName: string): void {}
}
class Customer {
}
const john = new Customer();
const basket = new Order();
// 原来的处理方式 Order内部去newCustomer
basket.setCustomer("john");
//变为将值对象改为引用对象
basket.setCustomer(john);
其实我们的工厂模式专门就是做这件事的
//我用Java改过来的一段TS实现的简单工厂
abstract class Noodles {
/**
* 描述每种面条啥样的
*/
public abstract desc(): void;
}
class LzNoodles extends Noodles {
public desc(): void {
console.log("兰州拉面 上海的好贵 家里才5 6块钱一碗");
}
}
class PaoNoodles extends Noodles {
public desc(): void {
console.log("泡面好吃 可不要贪杯");
}
}
class GankouNoodles extends Noodles {
public desc(): void {
console.log("还是家里的干扣面好吃 6块一碗");
}
}
class SimpleNoodlesFactory {
public static TYPE_LZ: number = 1;//兰州拉面
public static TYPE_PM: number = 2;//泡面
public static TYPE_GK: number = 3;//干扣面
public static createNoodles(type: number): Noodles {
switch (type) {
case SimpleNoodlesFactory.TYPE_LZ:
return new LzNoodles();
case SimpleNoodlesFactory.TYPE_PM:
return new PaoNoodles();
case SimpleNoodlesFactory.TYPE_GK:
default:
return new GankouNoodles();
}
}
}
const noodles:Noodles = SimpleNoodlesFactory.createNoodles(SimpleNoodlesFactory.TYPE_GK);
noodles.desc();
如上代码非常清晰,我们将数字类型(值对象)通过工厂生产出了应用对象(Noodles实现类)。
4. Collapse Hierarchy(折叠继承体系)
描述🌾:当超类和子类之间无太大区别的时候,应将他们合为一体。
动机🐻:如果你曾经编写过继承体系,就会知道,继承体系很容易变得过分复杂。新重构继承体系,往往是将函数和字段在体系中上下移动。完成这些动作后,你很可能发现某个子类并未带来该有的价值,因此需要把超类与子类合并起来。
// 我们把eat回归了父类 把sleep强制留给了子类
abstract class Animal {
eat() {
console.log('eat')
}
abstract sleep(): void
}
// let animal = new Animal()
class Dog extends Animal {
constructor(name: string) {
super()
this.name = name
}
// 随意设置了些自己的属性和方法
public name: string = 'dog'
protected pro() {}
readonly legs: number = 4
static food: string = 'bones'
sleep() {
console.log('Dog sleep')
}
}
// console.log(Dog.prototype)
let dog = new Dog('wangwang')
console.log(Dog.food)
dog.eat()
class Cat extends Animal {
sleep() {
console.log('Cat sleep')
}
}
let cat = new Cat()
let animals: Animal[] = [dog, cat]
animals.forEach(i => {
i.sleep()
})
前端圈最好用的刷题工具
扫码 get 前端圈刷题神器,解锁800+道 有答案和解析的 前端面试真题,全部来自BAT、TMD等一二线互联网公司。

回顾第一天的文章👉 :《重构-代码整洁之道TypeScript版》第一天
每一次我们不会给大家写过多的重构规则,力求每天用几分钟时间真正去理解了重构。明天见~如果您有什么意见和反馈,欢迎您在下方留言。
作者 【老袁】
2020 年 07月 29日