CPU 内存 硬盘都是针对接口设计的,如果针对实现来设计,内存就要对应到具体的某个品牌的主板,明显不合理。
code
代码如下图所示:
总结
控制反转、依赖反转、依赖注入、依赖反转
SOLID 总结
下表是对 SOLID 在不同维度的比较,大家可以看看,然后结合上面阐述的,细细品味下。
原则
耦合度
内聚度
扩展性
冗余度
维护性
测试性
适应性
一致性
SRP
-
+
o
o
+
+
o
o
OCP
o
o
+
-
+
o
+
o
LSP
-
o
o
o
+
o
o
+
ISP
-
+
o
-
o
o
+
o
DIP
-
o
o
-
o
+
+
o
+代表增加,-代表降低,o代表持平。
KISS
英文:Keep It Simple and Stupid
中文:保持简单愚蠢
俗解:保持代码简单
QUESTION
代码行数越少就越简单吗?
不一定,如一些较长的正则表达式,三位运算符,这些都是违背了 KISS 原则的
代码逻辑复杂就违背 KISS 原则吗?
不一定,如果是复杂的问题,用复杂的方法解决,并不违反 KISS 原则
如何写出满足 KISS 原则的代码?
不要使用同事可能不懂的技术来实现代码;不要重复造轮子,要善于使用已经有的工具类库;不要过度优化;
如何判断是否满足 KISS 原则?
KISS 是一个主观的评判,可以通过 code review 来做,如果大多数同事对你的代码有很多疑问,基本就说明不够 KISS
code
如下图所示:
let a = b ? c : d ? e : f
let reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
30个问题,你会几个
IOC
(Inversion Of Control
)?DI
(Dependency Injection
)?IOC
和DI
有什么区别?KISS
原则吗?KISS
原则的代码?KISS
原则?DRY
吗?看完这些问题,是不是激动的一笔,激动了,说明你又要进步成长了。
行文方式
先哈两句,活跃一下气氛。然后通过提出问题,回答问题,然后结合生活例子和代码,来全方位阐述设计原则知识。
为什么要学习设计原则
给你的自由过了火
做人需要原则,那写代码的时候,大家有没有讲原则呢?
按照正常剧情,这时候有小伙伴要开始表演了:
哈哈哈哈,那我只能说,你是光,你是电,你是唯一的神话。
越下游,越自由
大家有没有这种感觉,没有没关系,我举几个例子,大家就明白了。
ui
设计师产出设计稿,给开发产品看,起码要有点人样当骄傲的前端工程师写完代码,然后
two days later
。惊喜的发现,自己写的代码已经不认识了,这就非常尴尬了。然心中窃喜,毕竟我们处于最下游,不存在把代码给谁阅读之说。最多也就是走下code review
。这种感觉是非常危险的,当我们处于非常下游的地方,也意味着我们非常自由,它有很多负面影响。
通过设计原则来约束这种过火的自由。
设计原则有哪些呢
图中设计原则一栏,涵盖了所有重要的设计原则,如
SOLID
、KISS
、YANGI
、DRY
、LOD
。活不多说,下面大家跟着我,一步步掌握设计原则吧!
用好设计原则的目的
上面说了为什么要学习设计原则,那大家再想一下,我们用好设计原则的目的是什么?
总结一句话就是: 降低软件开发的复杂度,让迭代的难度保持在合理区间内。
OK
, 说完目的,我们开始逐一介绍这些设计原则,小伙伴们请往下阅读。SOLID
这是第一个介绍,也是最重要的。
请记住:设计原则中,
SOLID
是重点, 而SO
是重点中的重点。S
名称
QUESTION
如何理解单一职责原则?
一个类或者模块只负责完成一个职责或者功能,不要设计大而全的类,要设计粒度小、功能单一的类。单一职责原则是为了实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护性。
如何判断职责是否足够单一?
职责是否设计得越单一越好?
单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚性。同时,类职责单一,类依赖的和被依赖的其他类也会变少来实现代码的高内聚、松耦合。但是,如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。
生活中例子
社会分工:写代码的,不会同时去写策划文档。
code 展示
可以把上面的函数看成是
hooks
, 一个函数(hooks
)完成一个功能。一个功能只由一个模块目录完成。
可以传很多参数,不符合单一职责原则,它是外观模式思想的体现。外观模式如下图所示:
总结
上面的问题回答,进行了总结,这里再补充句:
SRP
要结合业务场景去看待,角度不同,结果不同。O
QUESTION
什么是开闭原则?
软件实体(类、模块、函数)都应当对扩展具有开放性,但对于修改具有封闭性。
也就是说:添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。
怎么才算满足或违反开闭原则?
怎样的代码改动才被定义为扩展或者说是修改?
只要它没有破坏原有代码的正常运行,没有破坏原有的单元测试,我们就可以认为它是符合开闭原则的
修改代码就一定意味着违反开闭原则吗?
尽量让最核心、最复杂的那部分逻辑代码满足开闭原则
如何做到对扩展开放、修改关闭?
如何在项目中灵活运用开闭原则?
时刻具备扩展意识、抽象意识、封装意识
生活中例子
高考试卷:比如明天就要高考了,但是现在老师发现没法区分高分学生和低分学生,必须得在试卷里面增加两个难度比较大的题,但是明天就高考了,如果现在去修改高考中的试卷,显然是不合理的,所以经过思考,最好的办法就是给高考的试卷加一个附加题【你可以加附加题,但是你不能修改原来的卷子,这就是对扩展开放,对修改关闭】。
code
例子1:中间件
例子2:
function optional
例子3:插件
例子4:装饰器
总结
L
LSP:Liskov Substitution Principle 中文:里氏替换原则
额,这缩写怎么有点搞笑,嗯?
QUESTION
什么是里氏替换原则?
子类在设计的时候,要遵守父类的行为约定(或者叫协议)。父类定义了函数的行为约定,那子类可以改变函数的内部实现逻辑,但不能改变函数原有的行为约定。这里的行为约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。
如何判断是否满足里氏替换原则?
拿父类的单元测试去验证子类的代码。如果某些单元测试运行失败,就有可能说明,子类的设计实现没有完全地遵守父类的约定,子类有可能违背了里式替换原则。
生活中例子
盗版光盘: 原来人家的光盘是正版的,但现在你弄了一个盗版的光盘,我们有两张光盘,放到
DVD
里面,都可以单独运行【盗版光盘把正版光盘全部copy过来,子类父类行为预期一致】code
例子一: 代码执行重复,不符合
LSP
原则为什么不符合
LSP
呢?是因为:第一:
Student
类继承了People
类,同时修改了People
类的eat
方法,这时就违背了LSP
原则第二:没有遵循父类的设计,修改了输出
总结
design by contract
,按照协议来设计I
ISP:Interface Segregation Principle 中文:接口隔离原则
QUESTION
ANSWER
生活中例子
汽车 USB 插口: 汽车上有很多插口,但是你想插
usb
接口,你想让它有usb
功能,又想让它有三线插头的功能,这就是不科学的事情【每一个接口都应该有自己的一种角色,只负责自己的角色】。code
图一:
delete
是不常用且危险的操作,如果和login
放在一起,就存在被不需要调delete
的业务误调的可能,违背了ISP
原则。图二:
一个函数里面处理了很多逻辑,也违背了
ISP
原则。总结
兄弟们, 细细品,重在隔离。
D
QUESTION
什么是依赖反转(倒置)原则 ?
高层模块(
high-level modules
)不要依赖低层模块(low-level
)。高层模块和低层模块应该通过抽象(abstractions
)来互相依赖。除此 之外,抽象(abstractions
)不要依赖具体实现细节(details
),具体实现细节 (details
)依赖抽象(abstractions
)高层模块和低层模块是啥意识?
在调用链上,调用者属于高层,被调用者属于低层
如何理解反转两个字?
反转指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程可以通过框架来控制。流程的控制权从程序员反转到了框架
什么依赖被反转了?
高层模块被反转了
什么是控制反转
IOC (Inversion Of Control)
?控制反转,控制是指对程序执行流程的控制,在没有反转前,控制权在程序员手里,经过反转后,控制权到了框架手里。控制反转并不是一种具体的实现技巧,而是一个比较笼统的设计思想,一般用来指导框架层面的设计
什么是依赖注入
DI (Dependency Injection)
?依赖注入是一种具体的编码技巧。不通过
new()
的方式在类内部创建 依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递 (或注入)给类使用IOC
和DI
有什么区别?IOC
是设计思想,DI
是具体编码技巧生活中例子
CPU
内存 硬盘都是针对接口设计的,如果针对实现来设计,内存就要对应到具体的某个品牌的主板,明显不合理。code
代码如下图所示:
总结
控制反转、依赖反转、依赖注入、依赖反转
SOLID 总结
下表是对
SOLID
在不同维度的比较,大家可以看看,然后结合上面阐述的,细细品味下。+
代表增加,-
代表降低,o
代表持平。KISS
QUESTION
代码行数越少就越简单吗?
不一定,如一些较长的正则表达式,三位运算符,这些都是违背了
KISS
原则的代码逻辑复杂就违背
KISS
原则吗?不一定,如果是复杂的问题,用复杂的方法解决,并不违反
KISS
原则如何写出满足
KISS
原则的代码?不要使用同事可能不懂的技术来实现代码;不要重复造轮子,要善于使用已经有的工具类库;不要过度优化;
如何判断是否满足
KISS
原则?KISS
是一个主观的评判,可以通过code review
来做,如果大多数同事对你的代码有很多疑问,基本就说明不够KISS
code
如下图所示:
总结
YANGI
生活中例子
双11剁手: 卧槽,好便宜啊,下单下单下单,然后,没有然后了...自行想象。
总结
DRY
Don’t Repeat Yourself
QUESTION
重复的代码就一定违背
DRY
吗?重复的代码不一定违背
DRY
原则,代码重复有三种典型情况,分别是实现逻辑重复、功能语义重复、代码执行重复如何提高代码的复用性?
减少代码耦合、满足单一职责原则、模块化、业务与非业务逻辑分离、通用代码抽离、抽象和封装、使用设计模式
code
例子一: 实现逻辑重复
代码1:
代码2:
1
不违背DRY
,代码2
违背了DRY
的初衷。SRP
和ISP
。例子2:功能语义重复
都是表达
hello
的意识,虽然代码没重复,但是语义重复了,违背DRY
例子3:代码执行重复
在一个函数中,多次执行同一个函数,违背了
DRY
总结
LOD
英文:
Law of Demeter
中文:迪米特法则 俗解:高内聚、松耦合QUESTION
什么是迪米特法则?
高内聚、松耦合是什么意识?
如何理解高内聚和松耦合?
它是一个通用的设计思想,可以用来指导不同粒度代码的设计与开发,比如系统、模块、类,甚至是函数,也可以应用到不同的开发场景中,比如微服务、框架、组件、类库等。
如何用好迪米特法则?
减少代码耦合、满足单一职责原则、模块化
生活中例子
现实中的对象:你对你的对象肯定了解的很多,但是你要是对别人的对象也了解很多,那就出大事了【一个对象应该对其他对象有尽可能少的了解】。
图解
code
如上图所示:我们用
lerna
去开发一个框架,将框架的不同功能放到不同的package
中进行维护迭代,符合LOD
。总结
高内聚、松耦合是一个非常重要的设计思想,能够有效地提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。
设计原则总结
看到这,设计原则基本就阐述完了,我对主要的设计原则进行了阐述,大家读会发现,一些设计原则虽然起名不同,但是其目标都是类似和相同的。学习和掌握主要的设计原则,可以帮助我们更好的进行软件设计、开发和迭代。也是为我们学习和掌握设计模式打下坚实的基础。
交流
欢迎加我微信进行技术交流。
也可以进 前端狂想录群 大家一起头脑风暴。