昨天我们发了第一篇,今天让我们来继续第二天。先来回顾一下昨天我们都实现了哪些:
(图片:贡嘎山)
简单理解就是不改变软件可观察行为的前提下,改善其内部结构,以提高理解性和降低修改成本。
描述?:你有一个引用对象,很小且不可改变,而且不易管理。
动机?:在分布系统和并发系统中,不可变的值对象特别有用,因为你无需考虑他们的同步问题。
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组件了。
描述?:从一个类衍生出许多彼此相等的实例,希望将他们替换为同一对象。
动机?:许多系统中,像日期、数量、名称等他们完全由所含的意义来标识,你并不在乎他们有多少副本。如果在你的副本中需要某一对象的变化影响到引用了他的地方,就需要考虑将这个对象变成一个引用对象。
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实现类)。
描述?:当超类和子类之间无太大区别的时候,应将他们合为一体。
动机?:如果你曾经编写过继承体系,就会知道,继承体系很容易变得过分复杂。新重构继承体系,往往是将函数和字段在体系中上下移动。完成这些动作后,你很可能发现某个子类并未带来该有的价值,因此需要把超类与子类合并起来。
// 我们把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()
})