大家好,这段时间上海的疫情太严重了,我们这些周边城市也收到了一些波及,是不是的部分区域就被封锁…期望上海的疫情早日过去,加油,每一位打工人…最后,求关注,求收藏,求点赞,任何意见都可以留言,谢谢…
前言
在上一章节中我们尝试在解决代码中关于深层嵌套这个问题(上一章节地址:《代码规范》如何写出干净的代码(三)解决代码深层嵌套的问题),那么在这一章节,则是要主要对 对象和类 做一些分享和处理;
再次明确,干净代码 的目的是为了编写可读且容易理解的代码,便于他人进行维护和接手,而设计模式等等的目的是为了编写可维护可扩展的代码,设计模式带来的代码不一定是干净代码,可能在可读性上会非常复杂,需要视情况而定;
耐心看完,你一定有所收获;
区分具体作用
区分具体作用?可能有小伙伴突然看到这个会有点懵,为什么类与对象的博客会出现这种章节,个人觉得对象与对象,类与类之间根据其用途是有明显区别的,一旦混用就会造成代码不再那么干净,直接看例子吧
const Database = {
connect() {
// ...业务代码连接数据库
},
disconnect() {
// ...业务代码关闭数据库
},
};
const user = {
name: "oliver yin",
age: 18,
address: "",
phone: "",
qq: "752746873",
email: "[email protected]",
};
这两个例子都是关于对象的使用,各位小伙伴觉得这两个对象在用途上是同一个类型吗,我感觉不大像吧,上面一个明显是一个用于实际业务场景的对象,里面包含了具体场景的业务代码,而下面则那个 有点数据容器的意思,只是用来存储数据了;
因此,在个人觉得对象与对象,类与类之间也是不同的,这两者得区分开来,如果混在一起写感觉略微有点糟糕
// 为了更好的展示吧Database改写成class的方式
class Database {
constructor(url) {
// 初始化
this.url = url;
}
connect() {
// ...业务代码连接数据库
}
disconnect() {
// ...业务代码关闭数据库
}
}
正常情况是这样的,但是一旦混写,那么针对这个Database就会可能变成下面这样
class Database {
constructor(url) {
// 初始化
this.url = url;
this.user = null;
}
connect() {
// ...业务代码连接数据库
}
disconnect() {
// ...业务代码关闭数据库
}
getUser() {
// ...获取用户信息后的返回值为res
this.user = res;
}
}
首先类名肯定是不再合适了,职责也不再单一了,混写后代码量那肯定也是直线上升,因此在写 类 或者 对象 的时候首先要明确的就是其作用,到底是 用于业务逻辑,还是仅 用于数据存储;
多态性
啊哈…理论就不多说了,直接看例子吧,这个例子应该是很多包括我在内的小伙伴们写代码时遇到的
class User {
constructor(type) {
this.type = type;
}
get() {
if (this.type === "admin") {
// ...业务代码
} else {
// ...业务代码
}
}
set() {
if (this.type === "admin") {
// ...业务代码
} else {
// ...业务代码
}
}
}
这是一个关于用户的class类,作用很简单,就是获取 / 设置的时候需要先判断一下是否是管理员,很明显对于管理员是有额外操作的,等到我们使用的时候只需要直接实例化就可以了
// 实例化
const user = new User(type);
对于这种结构,其实在 阅读性 上我个人觉得是没有任何问题,非常易于阅读,只是可扩展性和可维护性上稍微有些不足,那么如果改造怎么改造呢?
- 首先肯定是要 分层 了,把不同的用户类型都拆分出去,原因很简单因为任何一个系统都不可能只有两种用户类型,以后肯定会有更多,那么为了可扩展,就必须将每一个用户类型单独存在,就像这样
class Admin {
get() {
// ...业务代码
}
set() {
// ...业务代码
}
}
class Member {
get() {
// ...业务代码
}
set() {
// ...业务代码
}
}
- 第二个就是要 继承共有属性了,在原类里面区别不同类型靠的是type这个属性,也就是说type这个属性贯穿了这个逻辑结构,那么这个我们可以将其作为一个父类继承;
class Type {
constructor(type) {
this.type = type;
}
}
class Admin extends Type {
get() {
// ...业务代码
}
set() {
// ...业务代码
}
}
class Member extends Type {
get() {
// ...业务代码
}
set() {
// ...业务代码
}
}
到这里可能会有小伙伴说,这个用法和原来不一样了啊,原来只需要new一次就可以使用了,这里怎么使用呢,new不同的类吗,别急
- 第三步就是创建一个工厂函数用来做统一的入口了
function createUser(type) {
if (type === "admin") {
return new Admin(type);
} else {
return new Member(type);
}
}
const user = createUser(type);
到这里改造就差不多了,改造完后的优势就是我们只需要根据工厂函数就可以返回我们需要的实例,并且以后如果要扩展一个类型的用户只需要新增一个类,而不是去改造已存在的类;
单一职责
这一点和函数非常类似,也就是每一个类都是在专注处理一件事情,而不是将多个事项混合在一个类里面写成了一个超级大类,如果在一个大类里面,在可维护性,可阅读性上就会相对较差,看个例子吧
class Shop {
addProduct(title, price) {
}
updateProduct(productId, title, price) {
}
removeProduct(productId) {
}
getAvailableItems(productId) {
}
restockProduct(productId) {
}
createCustomer(email, password) {
}
loginCustomer(email, password) {
}
makePurchase(customerId, productId) {
}
addOrder(customerId, productId, quantity) {
}
refund(orderId) {
}
updateCustomerProfile(customerId, name) {
}
}
这是一个关于商城的类,里面有各种新增,修改,删除,登录等等操作,因此这个类在最终会非常庞大,在可阅读性和可维护性上就会略微有所欠缺,那么就需要进行拆分改造,改造完差不多就是如下:
class Order {
refund() {
}
}
class Customer {
constructor(email, password) {
}
login(email, password) {
}
updateProfile(name) {
}
makePurchase(productId) {
}
}
class Product {
constructor(title, price) {
}
update(Id, title, price) {
}
remove(Id) {
}
}
class Inventory {
getAvailableItems(productId) {
}
restockProduct(productId) {
}
}
我们将类根据 实际功能 进行了拆分,比如订单相关的就是Order类,用户相关的就是Customer类,而产品相关的就是Product相关的类等等,进行拆分以后,根据明显就是清晰了很多,在阅读性,可读性,可维护性上就明显有大幅提升;
一些原则
想了想,好像干净代码在一定程度上和设计模式怎么都绕不开,因此单独写写一下这部分吧
最小知识原则
又叫得墨忒(te)尔定律,它的意思是:对象较少依赖于其他对象的内部结构,简单的说就是不要过分过于依赖其他对象内部的属性,比如
this.user.time.register
这种就是不符合得墨忒尔定律,也许我们在使用的时候知道一定存在this.user属性,但是我们不确定this.user上是否存在time这个对象,更不用说time上是否存在register这个属性了,因此如果嵌套过多,那么这个代码的健壮性就不够优雅,看个例子吧
class Customer {
getTime(type) {
return this.time[type];
}
}
class DeliveryJob {
constructor(customer, warehouse) {
this.customer = customer;
this.warehouse = warehouse;
}
deliverLastPurchase() {
// const date = this.customer.time.register
this.warehouse.deliverPurchases(this.customer.getTime("register"));
}
}
改造后对象只能访问自己内部的属性,而对于其他对象内部的属性则需要通过方法进行访问;
单一职责原则
这个职责其实我们在上面已经分享过了,简单的说就是一个函数,或者一个类只承担一种类型的责任,一旦超出这个界限的时候,那么就需要进行拆分,这么做最直接对干净代码的好处就是在代码量上会得到有效的控制,并且阅读性也有所提高;
就拿之前的例子来说,一开始shop这个类非常庞大,它包罗万象,囊括了所有商场里面的所有方法,这种就是没有拆分,后来我们依据功能为维度进行了拆分,分成了用户,订单,商品等等多个业务功能的类,具体请查看《单一职责》这一章节;
开闭原则
开闭原则简单的说就是:对扩展开发,而对修改封闭,这点有点像是上面《多态性》这一章节的演示
class Print{
veriftyData(){
}
WebPrinter(){
// ...业务代码
}
PDFPrinter(){
// ...业务代码
}
}
假设现在有一个类Print,到目前为止,它里面有3个方法,分别是验证,web打印,PDF打印,其中打印前都需要先调研veriftyData这个函数做校验,但是我感觉上讲,以后的需求大概率是要加打印方式的,如果新需求说要加一个新的打印方式,比如excel,那么这时候这种写法就需要去修改Print这个类,根据开闭原则,这种就不合适,它不允许直接去修改源码,那么怎么改呢,其实就是如多态性那一章的做法
class Print {
veriftyData() {
}
}
class WebPrinter extends Print {
// ...业务代码
}
class PDFPrinter extends Print {
// ...业务代码
}
可以通过继承来扩展新需求,但是对于父类Print则不允许修改;
小结
本文可能分享的内容有点多并且也不全是关于对象和类的,大致如下:
- 在写对象或者类的时候一定 要区分其作用,到底是用来做业务处理的还是数据存储的;
- 根据单一职责的约束,我们要根据其功能多写小类,最终进行组合与拼装,而不是一个大类,大类在可阅读性和可维护性上相对略差;
- 多态要求我们在统一入口的前提下拆分类或者对象,可以便可以大幅提高可维护性,可扩展性;
- 最后分享了一些有助于编写干净代码的设计模式;