一 接口和面向接口编程
1 用ts编写基于 interface
的命令模式
编写用户界面程序,页面有成百上千个子菜单
约定基于命令模式编写
- 负责子菜单的同事 完成编程之后会将子菜单封装成一个命令对象,将其交给编写菜单集合界面的同事
约定:调用子菜单的
execute
方法时会执行对应子菜单的命令
class RefreshMenuBarCommand {
execute() {
console.log('刷新菜单界面');
}
}
class AddSubMenuCommand {
execute() {
console.log('增加子菜单');
}
}
class DelSubMenuCommand {
execute() {
console.log('删除菜单界面');
}
}
let refreshMenuBarCommand = new RefreshMenuBarCommand()
let addSubMenuCommand = new AddSubMenuCommand()
let delSubMenuCommand = new DelSubMenuCommand()
let setCommand = command => {
document.getElementById('execCommand').onclick = () => commond.execute()
}
setCommand(refreshMenuBarCommand)
setCommand(addSubMenuCommand)
setCommand(delSubMenuCommand)
防止某个子命令对象没有实现 execute
方法,在高层函数中添加一段防御性代码
let setCommand = command => {
document.getElementById('execCommand').onclick = () => {
if(typeof command.execute !== 'function') {
throw new Error('command对象必须实现execute方法')
}
commond.execute()
}
}
2 ts版本的命令模式
1. 定义 Command
接口
interface Command {
execute: Function;
}
2. 定义类
class RefreshMenuBarCommand implements Command {
constructor() {}
execute() {
console.log('刷新菜单界面');
}
}
class AddSubMenuCommand implements Command {
constructor() {}
execute() {
console.log('增加子菜单');
}
}
class DelSubMenuCommand implements Command {
constructor() {}
execute() {
console.log('删除菜单界面');
}
}
二 代码重构
1 提炼函数
- 避免出现超大函数
- 独立出来的函数
- 有助于代码复用
- 更容易被覆写
- 如果拥有一个良好的命名,它本身就起到了注释的作用
2 合并重复的条件片段
function paging(currPage) {
if(currPage <= 0) {
currPage = 0;
jump(currPage);
} else if(currPage >= totalPage) {
currPage = totalPage;
jump(currPage);
} else {
jump(currPage);
}
}
独立重复代码
function paging(currPage) {
if(currPage <= 0) {
currPage = 0;
} else if(currPage >= totalPage) {
currPage = totalPage;
}
jump(currPage);
}
3 把条件分支语句提炼成函数
规则:夏季全部商品打八折出售
function getPrice(price) {
let date = new Date();
if(date.getMonth() >= 6 && date.getMonth() <= 9) {
return price * 0.8;
}
return price;
}
提炼条件分支语句
更准确地表达代码的意思,函数名本身又能起到注释的作用
function isSsummer() {
let date = new Date();
return date.getMonth() >= 6 && date.getMonth() <= 9;
}
function getPrice(price) {
if(isSummer()) {
return price * 0.8;
}
return price;
}
4 合理使用循环
5 提前让函数退出代替嵌套条件分支
函数只有一个出口
function del(obj) {
let ret;
if(!obj.isReadOnly) {
if(obj.isFolder) {
ret = deleteFolder(obj);
} else if(obj.isFile) {
ret = deleteFile(obj);
}
}
return ret;
}
翻转外层if判断
function del(obj) {
let ret;
if(obj.isReadOnly) {
return;
}
if(obj.isFolder) {
ret = deleteFolder(obj);
} else if(obj.isFile) {
ret = deleteFile(obj);
}
return ret;
}
6 传递对象参数代替过长的参数列表
7 尽量减少参数数量
8 少用三目运算符
9 合理使用链式调用
让方法调用结束后返回对象自身
class User {
constructor() {
this.id = null;
this.name = null;
}
setId(id) {
this.id = id;
return this;
}
setName(name) {
this.name = name;
return this;
}
}
console.log(
new User().setId(12).setName('lisi')
);
- 若其中一步出错,增加调试难度
- 若链条容易发生改变,导致调试和维护困难
10 分解大型类
街头霸王
class Spirit {
constructor(name) {
this.name = name;
}
attack(type) {
if(type === 'waveBoxing') {
console.log(this.name + ': 使用波动拳');
} else if(type === 'whirlKick') {
console.log(this.name + ': 使用旋风腿');
}
}
}
- attack 方法过于庞大 -> 有必要作为一个单独的类
class Attack {
constructor(spirit) {
this.spirit = spirit;
this.list = {};
this.init()
}
start(type) {
return this.list[type].call(this)
}
init() {
this.list = {
waveBoxing() {
console.log(this.spirit.name + ': 使用波动拳');
},
whirlKick() {
console.log(this.spirit.name + ': 使用旋风腿');
}
}
}
}
class Spirit {
constructor(name) {
this.name = name;
this.attackObj = new Attack(this);
}
attack(type) {
this.attackObj.start(type);
}
}
11 return 退出多重循环
- break -> return
- 把循环后面的代码放到return后面
- 代码多 -> 提炼成函数