类
单一职责原则
单一职责原则是面向对象设计原则,为了降低复杂性,耦合性和提高可维护性。一个类只承担一项职责与功能,承担的功能更多会让可读性降低。
实现单一职责的原则
1.识别和分离职责
识别类中是否存在多个功能。
2.分解类
识别出多功能,下一步分解功能到不同的类中。
js
// 反例
user类既维护了又用户信息做对用户做了校验。
class User {
constructor(name, email, password) {
this.name = name;
this.email = email;
this.password = password;
}
getUserInfo() {
return { name: this.name, email: this.email };
}
authenticate(providedPassword) {
return providedPassword === this.password;
}
}
//正例
// 分解成2个类 区分用户信息和校验
class UserInfo {
constructor(name, email) {
this.name = name;
this.email = email;
}
getUserInfo() {
return { name: this.name, email: this.email };
}
}
class UserAuthentication {
constructor(password) {
this.password = password;
}
authenticate(providedPassword) {
return providedPassword === this.password;
}
}
开/闭原则
代码实体(类,模块,函数等)应该易于扩展,难于修改以适应新的需求
js
// 反例
class AjaxRequester {
constructor() {
// What if we wanted another HTTP Method, like DELETE? We would have to
// open this file up and modify this and put it in manually.
this.HTTP_METHODS = ['POST', 'PUT', 'GET'];
}
get(url) {
// ...
}
}
// 正例:
class AjaxRequester {
constructor() {
this.HTTP_METHODS = ['POST', 'PUT', 'GET'];
}
get(url) {
// ...
}
addHTTPMethod(method) {
this.HTTP_METHODS.push(method);
}
}
实现类的开闭原则也会涉及到几个关键的设计模式和编程实践,如工厂模式,策略模式组合模式。
里氏替换原则
子类可以扩展父类的功能,但不能改变父类原有的功能。 遵循此原则的编码建议:
- 子类应通过添加新方法或重写父类抽象方法扩展功能,而非修改父类抽象方法。
- 参数与返回类型兼容:子类重写父类方法时参数应更宽松(支持多种类型),返回值应更严格。
- 使用抽象类或接口:通过抽象类或接口定义稳定的部分,将可变的部分封装在具体实现中避免破坏原则。
js
示例:
class Animal {
constructor () {
}
makeSound(...animals) {
for(let animal of animals) {
if (animal instanceof Animal) {
console.log(animal.name, animal.makeSound())
} else {
console.log('拒绝表演,下次别来了')
}
}
}
}
class Dog extends Animal{
constructor (name) {
this.name=name
}
makeSound() {
console.log("汪汪");
}
}
class Cat extends Animal{
constructor (name) {
this.name=name
}
makeSound() {
console.log("喵");
}
}
let Animal =new Animal()
Animal.makeSound(new Dog('小狗'),new Cat('小喵'));
// 这里Animal.makeSound方法不依赖小狗和小喵的实例还是依赖 Animal实例;;小狗/小喵的 makeSound 方法的实现就是里氏替换。
接口隔离原则
在 JS 中,当一个类需要许多参数设置才能生成一个对象时,或许大多时候不需要设置这么多的参数。此时减少对配置参数数量提高系统的灵活性和可维护性。
接口隔离原则的核心思想
- 单一职责原则:每个接口应该只包含一个客户端(使用者)使用的方法,这样可以避免客户端依赖于不需要的方法。
- 高内聚、低耦合:通过将接口拆分成更小的部分,可以减少类之间的耦合,提高系统的内聚性。
js
// 反例
class MultiFunctionPrinter {
print() {}
scan() {}
fax() {}
staple() {}
}
// 正例 - 接口隔离
class Printer {
print() {}
}
class Scanner {
scan() {}
}
class FaxMachine {
fax() {}
}
class Stapler {
staple() {}
}
// 组合功能
class AllInOnePrinter {
constructor(printer, scanner, faxMachine) {
this.printer = printer;
this.scanner = scanner;
this.faxMachine = faxMachine;
}
print() {
this.printer.print();
}
scan() {
this.scanner.scan();
}
fax() {
this.faxMachine.fax();
}
}
依赖反转原则
高层模块不应该依赖低层模块 里氏替换实例中 ,Animal.makeSound方法 就是依赖自身的方法。不依赖底层类方法
js
示例
// 抽象的日志记录器接口
class Logger {
log(message) {
throw new Error("Abstract method must be implemented");
}
}
// 具体的控制台日志记录器实现
class ConsoleLogger extends Logger {
log(message) {
console.log(message);
}
}
// 业务逻辑类,依赖于抽象的日志记录器
class BusinessLogic {
constructor(logger) {
this.logger = logger;
}
doSomething() {
let result = "Some operation result";
this.logger.log(result);
}
}
// 使用控制台日志记录器注入到业务逻辑类
let business = new BusinessLogic(new ConsoleLogger());
business.doSomething();
使用方法链
js
// 反例
class Car {
constructor() {
this.make = 'Honda';
this.model = 'Accord';
this.color = 'white';
}
setMake(make) {
this.name = name;
}
setModel(model) {
this.model = model;
}
setColor(color) {
this.color = color;
}
save() {
console.log(this.make, this.model, this.color);
}
}
let car = new Car();
car.setColor('pink');
car.setMake('Ford');
car.setModel('F-150')
car.save();
//正例
class Car {
constructor() {
this.make = 'Honda';
this.model = 'Accord';
this.color = 'white';
}
setMake(make) {
this.name = name;
// NOTE: Returning this for chaining
return this;
}
setModel(model) {
this.model = model;
// NOTE: Returning this for chaining
return this;
}
setColor(color) {
this.color = color;
// NOTE: Returning this for chaining
return this;
}
save() {
console.log(this.make, this.model, this.color);
}
}
let car = new Car()
.setColor('pink')
.setMake('Ford')
.setModel('F-150')
.save();
处理并发
用promise 替代回调
回调不够整洁可能会造成大量嵌套 ES6内置了promise直接使用,promise的使用提高代码整洁性和可维护性
js
//反例
require('request').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', function(err, response) {
if (err) {
console.error(err);
}
else {
require('fs').writeFile('article.html', response.body, function(err) {
if (err) {
console.error(err);
} else {
console.log('File written');
}
})
}
})
//正例
require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
.then(function(response) {
return require('fs-promise').writeFile('article.html', response);
})
.then(function() {
console.log('File written');
})
.catch(function(err) {
console.error(err);
})
Async/Await 是较 Promises 更好的选择
promise是相较回调来说是一种更好的选择,ES7中的await和async 更胜于promise
js
示例
async function getCleanCodeArticle() {
try {
var request = await require('request-promise')
var response = await request.get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
var fileHandle = await require('fs-promise');
await fileHandle.writeFile('article.html', response);
console.log('File written');
} catch(err) {
console.log(err);
}
}
错误处理
使用try/catch 捕获错误并且应当对 catch捕获的报错做出相应的处理方案
js
// 反例:
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}
// 正例
try {
functionThatMightThrow();
} catch (error) {
// One option (more noisy than console.log):
console.error(error);
// Another option:
notifyUserOfError(error);
// Another option:
reportErrorToService(error);
// OR do all three!
}
promise的catch捕获的报错做出相应的处理方案 与try/catch 同理
格式化
- 大小写一致,针对团队制定的统一规则保持风格一致.
- 调用函数和被调用的函数应该放在相近的位置.