《重构-代码整洁之道TypeScript版》第2天

时间:2020-7-30 作者:admin

昨天我们发了第一篇,今天让我们来继续第二天。先来回顾一下昨天我们都实现了哪些:

  • Add Parameter(添加参数)  
  • Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) 
  • Change Unidirectional Association to Bidirectional(将单向关联改为双向关联) 

《重构-代码整洁之道TypeScript版》第2天

(图片:贡嘎山)

什么是重构 ?

简单理解就是不改变软件可观察行为的前提下,改善其内部结构,以提高理解性和降低修改成本。


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.jsImmutable.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版》第2天


回顾第一天的文章👉 :《重构-代码整洁之道TypeScript版》第一天

每一次我们不会给大家写过多的重构规则,力求每天用几分钟时间真正去理解了重构。明天见~如果您有什么意见和反馈,欢迎您在下方留言。

作者 【老袁】
2020 年 07月 29日

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。