《重构-代码整洁之道TypeScript版》第4天
今天让我们来继续第4天,老规矩先来回顾一下昨天我们都实现了哪些:
- Consolidate Conditional Expression(合并条件表达式)
- Consolidate Duplicate Conditional Fragments(合并重复的条件片段)
- Convert Procedural Design to Objects(将过程化设计转化为对象设计)
(图片:梅里雪山)
什么是重构 ?
简单理解就是不改变软件可观察行为的前提下,改善其内部结构,以提高理解性和降低修改成本。
1. 这是如下我们要实现的目标任务列表(每天进步一点点⏰)
- [x] Decompose Conditional(分解条件表达式)
- [x] Duplicate Observed Data(复制“被监视数据”)
- [x] 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. Decompose Conditional(分解条件表达式)
描述?:你有一个复杂的if..else if ...else语句,可以把它从复杂的代码中提取出来
动机?:在业务开发中,你必须编写代码来检查不同的条件分支、根据不同的分支做不同的事,然后你就会得到一个相当长的函数,大型函数自身就会使代码的可读性下降,而条件逻辑则会使代码更难阅读。
//假设我要计算购买某样商品的总价(总价=数量X单价),而这个商品在冬季和夏季的单价是不同的:
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
charge = quantity * _winterRate + _winterServiceCharge;
} else {
charge = quantity * _summerRate;
}
我把每个分支的判断条件都提炼到一个独立函数中,如下所示(伪代码):
if(Summer(date)){
charge = winterCharge(quantity);
}else{
charge = summerCharge(quantity);
}
// ...如下代码在类中
private Summer(date:Date):boolean{
return date.before(SUMMER_START) || date.after(SUMMER_END);
}
private summerCharge(quantity:number):number{
return quantity*summerRate;
}
private winterCharge(int quantity){
return quantity* winterRate+_winterServiceCharge;
}
通过如上代码我们能看到整个结构更加清晰。还有一个神来之笔就是用字典了。
const result = 'a';
// bad
if(result == "a"){
...
}else if(result == "b"){
...
}else{
...
}
//best 我们采用字典
interface IDictionaries {
[key: string]: any;
}
const obj: IDictionaries = {
a: () => {},
b: () => {},
};
const fn = obj[result];
fn && fn();
3. Duplicate Observed Data(复制“被监视数据”)
描述?:你有一些数据置身在GUI控件中(HTML页面)中,而领域函数(在服务端、NW开发的PC端等)需要访问这些数据。将该数据复制到一个领域对象中,建立一个Observer模式,用以同步领域对象和GUI对象内的重复数据。
上文提到了领域对象,对于很多Web前端的同学是很模糊的,我们来一起学习下吧。
“领域对象(Domain Object)也被称为实体类,它代表了业务的状态,且贯穿展现层、业务层和持久层,并最终持久化到数据库,如果只是简单的理解的话,领域对象可以看做是数据库表的对应java类。如果对应到大家的前端开发里也就是我们Vue里的data啦。
说到了领域对象就一定要谈一下领域驱动模型。业务逻辑都是写在Service中的,WmActPoi充其量只是个数据载体(Vue里的data只是定义了数据的基本形态,全部业务处理逻辑都堆积在了method里)没有任何行为,是一种贫血模型,下图呢就是DDD的整体运行流程。
采用DDD的设计思想,业务逻辑不再集中在几个大型的类上,而是由大量相对小的领域对象(类)组成,这些类具备自己的状态和行为,每个类是相对完整的独立体,并与现实领域的业务对象映射。领域模型就是由这样许多的细粒度的类组成。如下我贴上一段非常核心的TypeScript实战DDD的代码。
看不懂的同学大家可以关注下@node-ts/ddd
// user.ts
import { AggregateRootProperties, AggregateRoot, Uuid } from '@node-ts/ddd'
import { UserRegistered, UserPasswordChanged, UserDisabled } from './events'
import { OAuthService } from './services'
export interface UserProperties extends AggregateRootProperties {
email: string
isEnabled: boolean
passwordChangedAt: Date | undefined
}
export class User extends AggregateRoot implements UserProperties {
email: string
isEnabled: boolean
passwordChangedAt: Date | undefined
// Creation static method. Aggregates are never "newed" up by consumers.
static register (id: Uuid, email: string): User {
const userRegistered = new UserRegistered(
id,
email,
true
)
const user = new User(id)
// event is applied to the user object
user.when(userRegistered)
return user
}
/**
* Changes the user's password that's used to log in to the site
* @param oauthService the oauth service that hosts the user account
* @param newPassword password the user wants to use
*/
async changePassword (oauthService: OAuthService, newPassword: string): Promise<void> {
// A domain service is used to perform the actual change of password
await oauthService.changePassword(this.id, newPassword)
const userPasswordChanged = new UserPasswordChanged(
this.id,
new Date()
)
super.when(userPasswordChanged)
}
/**
* Disable the user account so they can no longer log in
*/
disable (): void {
const userDisabled = new UserDisabled(this.id, false)
super.when(userDisabled)
}
protected whenUserRegistered (event: UserRegistered): void {
this.email = event.email
this.isEnabled = event.isEnabled
}
protected whenPasswordChanged (event: UserPasswordChanged): void {
this.passwordChangedAt = event.passwordChangedAt
}
protected whenUserDisabled (event: UserDisabled): void {
this.isEnabled = event.isEnabled
}
}
动机?:一个分层良好的系统,应该将处理用户界面和处理业务逻辑的代码分开。若果你遇到的代码是以两层方式开发,业务逻辑被内嵌在用户的界面之中,你就有必要将行为分离出来,其中重要的任务就是函数的分解和搬移。但数据就不同了:你不能仅仅只是移动数据,必须将它复制到新的对象中,并提供相同的同步机制。
// 其实这个规则说成前端的大白话就是如果一个数据前后都需要 就把他设置成可Observable
// 目前的前端开发处理这个有Vue 、Mobx等等。但是单独处理这个有Rx.js
const locations = new RX.Observable((observer) => {
let watchId: number;
if ('geolocation' in navigator) {
watchId = navigator.geolocation.watchPosition((position: Position) => {
observer.next(position);
}, (error: PositionError) => {
observer.error(error);
});
} else {
observer.error('Geolocation not available');
}
return {
unsubscribe() {
navigator.geolocation.clearWatch(watchId);
}
};
});
const locationsSubscription = locations.subscribe({
next(position:Position) {
console.log('Current Position: ', position);
},
error(msg:string) {
console.log('Error Getting Location: ', msg);
}
});
setTimeout(() => {
locationsSubscription.unsubscribe();
}, 10000);
如上的demo监听的是定位信息,当然这个定位也可以是后端发起请求的数据,说白了多端一起监听这个locations,而不是分散到各自的类中各自维护。
4. Encapsulate Collection(封装集合)
描述?:让一个函数返回该集合的一个只读副本,并在这个类中提供添加、移除集合元素的函数。
动机?:我们常常会在一个类中使用集合(Map、Set、Array),通常这样的类也会提供针对该集合的取值、设值函数。这个时候,取值函数不应该返回集合本身,因为这样会让用户得以修改集合内容而集合拥有者缺一无所知,这样也会暴露过多对象内部数据结构的信息。另外设置函数不应该提供添加修改和移除的操作,但不能直接重写该集合。如果做到了这些集合就被很好的封装了起来,这样便可以降低集合永州这和用户之间的耦合度。
这个规则不是很复杂,老袁就不给大家列具体的 code了。
每一次我们不会给大家写过多的重构规则,力求每天用几分钟时间真正去理解了重构。
- 前台开发从头说起:理解css盒模型
- 两个js冲突怎么解决?试试这四个方法
- dedecms如何去除后台登陆验证码
- DEDECMS自定义表单unix时间戳转换成常规时间方法及增加表单添加时间方法
- dedecms自定义表单发布成功后返回当前页面
- 前端构建工具 Gulp.js 上手实例
- dedecms数据库内容替换安全确认码不显示怎么解决
- 利用宏避免发送确认邮件时忘记添加附件
- dateDiff在Objective-C中的实现
- 禁用Firefox自带的元素查看工具
- 容易被误解的overflow:hidden
- dedecms调用全站相关文章怎么设置
- dedecms自定义表单提交成功后提示信息修改和跳转链接修改
- dede:arclist orderby=weight dedecms列表页文章按权重排序无效问题
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- KDE下完美的Unity桌面体验,扔掉active window control
- C++核心准则T.5:结合使用泛型和面向对象技术应该增强它们的效果而不是成本
- C++核心准则T.10:为所有的模板参数定义概念
- C语言二级指针用法之模拟句柄用途
- Linux解压缩文件
- C++核心准则T.11:只要可能就使用标准概念
- 给pugjs的stun主题添加canvas时钟
- C++核心准则T.12:声明局部变量类型时,概念比auto更好
- Arch Linux切换rEFInd开机引导程序
- C++核心准则T.13:对于简单的,单类型参数概念,使用缩略记法更好
- VBA编写Ribbon Custom UI编辑器08——实现ZIP的写入
- 用 Python 写个七夕表白神器
- 3分钟短文 | Laravel 用户授权原来内置了这么多方法
- E0144"const char *" 类型的值不能用于初始化 "char *" 类型的实体的三种解决方法
- 数据采集面对JS加密无所适从?善用这3个工具,搞定一大半的JS逆向!