单一职责原则:S
1.如何理解(SRP):
一个类只负责完成一个职责的功能,不要设计大而全的类,单一职责原则是为了实现代码的高内聚,低耦合,提高代码的可读性和复用性
2.如何判断一个类是否符合单一职责原则:
不同的应用场景,不同的阶段的需求背景,对同一个类的职责是否单一有不同的判定结果。
我们可以使用以下的小技巧来判断:
1.类中的代码行数,函数或者属性过多
2.类依赖的其他类过多,或者依赖类的其他类过多
3.私有方法过多
4.比较难给一个类起一个合适的名字,起的名字比较笼统,例如:manger类,context类
5.类中大量的方法都是集中操作某一些属性
3.类的职责是否设计的越单一越好呢?
并不是越设计的越单一越好,如果拆分的过细的话,实际上会降低内聚性,也会影响代码的可维护性
对外开放,修改关闭原则:O
1.如何理解:
添加一个功能,应该是通过已有代码的基础上扩展代码,而非修改代码的方法来完成。
注意:
1.开闭原则并非是说完全杜绝修改,而是以最小的修改来完成新功能的开发
2.同样的代码改动,在不同的代码粒度下可能理解的不同
2.如何做到:
我们时刻要具备扩展意识,抽象意识,封装意识。在写代码的时候,我们要多花些时间想想,这段代码在未来可能有哪些需求的变动,如何设计代码,事先留好扩展点,以便未来需求变更的时候,在极小的代码改动下,将新的功能插入到扩展点上。
3.最常用来提高代码扩展性的方法有
多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态)
里氏替换原则:L
* 理解:
1.用来指导继承关系中子类如何设计的一个原则,核心:“design by contarct:按照协议来设计”
2.子类完美继承父类的设计初衷,并做了增强
3.子类对象能够替换程序中父类对象出现的任何地方,并且保证原来的程序的逻辑行为不变及正确性不被破坏
* 具体体现:
父类定义了函数的“协议”,那么子类可以改变函数的**内部实现逻辑**,但是不能改变函数原有的“约定”
这里的约定包括:
1.父类函数声明要实现的功能,子类必须实现该功能
例:父类实现的函数是按照订单金额排序的,而继承的子类是按照下单时间排序的,这就违反了里氏替换原则
2.对父类定义的输入的数据,输出的数据,异常的约定不能违背
例1(输入):父类能输入任意的整数,而子类只能输入正整数这就违背了原则
例2(输出):父类的函数约定:运行出错的时候返回null,获取数据为空的时候返回空集合,而子类的实现变成了运行出错返回异常,获取数据为空返回null,这就违反了里氏替换原则
例3(异常):父类定义了只会抛出 ArgumentNullException 异常,那么子类必须只能抛出ArgumentNullException异常,其他的异常就违背了原则
3.注释中所罗列的任何特殊说明不能违背
例:父类函数的注释是:用户的提现金额不得超过账户余额,而子类重写函数后,针对vip用户实现提现金额可以大于账户余额。这就违反了里氏替换原则
* 与多态的区别:
虽然定义描述和代码实现有点类似,但是他们的关注点是不一样的,多态是面向对象的一大特性,它是一种代码实现的思路。而里式替换是一种设计原则,指导继承关系中子类该如何设计
* 判断的小技巧:
拿父类的单元测试去验证子类的代码。如果某些单元测试运行失败,就有可能说明,子类的设计实现没有完全地遵守父类的约定,子类有可能违背了里式替换原则
接口隔离原则:I
* 重点是理解“接口”二字,以下三种的不同的理解
1.“接口理解为一组接口集合”:
可以是某个微服务的接口或者某个类库的接口,如果这部分接口只被部分调用者来使用的话,我们就需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口
例如:
用户的删除接口,我们只希望给后台管理者使用,其他普通用户不能使用,这时候我们就需要把这接口给隔离出来
2.“接口理解为单个api接口或者函数”:
部分的调用者只需要函数中的部分功能,闹我们就需要把这个函数拆分成粒度更小的多个函数,让调用者只依赖它需要的那个细粒度函数
例如:
count()是完成求平均数,最大数,最小数的函数,那么如果有些用户只需要求平均数,不常用到最大数和最小数,那么我们就需要把函数拆分为max(),min(),avg()这三个粒度更小的函数
3.“接口理解为oop的接口”:
那么接口的设计要尽量的单一,不要让接口的实现类和调用者,依赖不需要的接口函数
* 接口隔离原则和单一职责原则的区别:
单一职责原则是对类,模块,接口的设计,接口隔离原则相对于单一职责原则,一方面侧重于接口的设计,另一方面思考的角度不同,它提供了一种判断接口的职责是否单一的一个标准:通过调用者如何使用接口来间接的判定,如果调用者只使用部分的接口或者接口的部分功能,那接口的设计就不够单一
依赖反转 D:
# 控制反转:
1.这里的“控制”指的是对程序的执行流程的控制,而“反转”指的是没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程可以通过框架来控制。流程额控制从程序员“反转”到了框架
2.它不是具体的实现技巧,而是笼统的设计思想,用于指导框架的设计
3.实现控制反转的方法有很多,例如:模板设计模式方法,依赖注入等(搜集)
4.案例分析:(模板设计的控制反转)
//测试必须实现的接口
public abstract class TestCase {
public voi d run() {
if(doTest()){
System.out.println("Testsucceed.");
}else{
System.out.println("Testfailed.");
}
}
public abstract voi d doTest() ;
}
//测试框架
public class JunitApplication {
private static final List<TestCase> testCases = new ArrayList<>() ;
public static void register(TestCase testCase) {
testCases.add(testCase) ;
}
public static final void main(Stri ng[] args) {
for(TestCase case: testCases) {
case.run();
}
}
}
//具体测试的类
publi c class UserServi ceTest extends TestCase {
@Overri de
publi c boolean doTest() {
// 主要测试的功能
}
}
# 依赖注入:
1.它是一种编程的技巧,不通过new()的方式在类的内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数,函数参数等方式传递(或注入)给类使用
2.这里的依赖类可以定义成接口,基于接口实现而非实现编程
3.它可以实现控制反转
# 依赖注入框架:
1.通过依赖注入框架提供的扩展点,简单配置一下所有需要的类及其类与类之间的依赖关系,就可以实现由框架来自动创建对象,管理对象的生命周期,依赖注入等原本需要程序员来做的事,全都交给框架来实现
2.Spring 框架的控制反转主要是通过依赖注入来实现的
# 依赖反转:(依赖倒置)
1.高层的模块不要依赖于底层的模块。高层模块和底层的模块应该通过抽象来相互依赖,除此之外,抽象不要依赖具体的实现细节,但是具体的实现细节依赖抽象
2.这里的高层模块简单来说,在调用链上,调用者属于高层模块,被调用者属于底层模块。
3.跟控制反转有点类似,主要用于指导框架的设计
4.案例分析:
1.Tomcat是javaweb容器,我们编写的web应用程序只要部署在tomcat容器中,便可以被tomcat容器调用执行。
2.这样的话,Tomcat就是高级模块,我们编写的应用程序属于底层模块。
3.Tomcat和应用程序之间并没有直接的依赖关系,两者都依赖于同一个抽象,也就是servlet规范,
4.servlet规范不依赖与具体的tomcat容器和应用程序的实现细节,而tomcat和应用程序依赖于servlet的规范
kiss原则和YAGNI原则:
# Kiss原则:尽量保持代码简单
# 代码少并不一定满足KISS原则,我们还是考虑逻辑的复杂度,代码的可读性等
# 本身负责的问题,用复杂的方法解决,并不违背kiss原则:
例如:KMP 算法本身具有逻辑复杂、实现难度大、可读性差的特点,但是对于当我们需要处理长文本字符串匹配问题,遇到系统性能瓶颈的时候我们应该考虑使用kmp算法
# 同样的代码,在某个业务场景下满足 KISS 原则,换一个应用场景可能就不满足了
我们平时开发使用普通的工具类的时候就可以,但是如果考虑系统系统性能的时候,这时候我们就需要自己实现工具类来提高性能
# 如何写出满足KISS原则的代码?
1.不要使用同时可能不懂的技术来实现代码
2.不要重复造轮子,要善于使用已经有的工具类
3.不要过度的优化,不要过度使用一些奇技淫巧(位运算,复杂的if-else)来优化代码,来牺牲代码的可读性
#建议:
在开发中,不要过度的设计,其实用简单的方法解决复杂的问题,更能体现一个人的能力
#YAGNI原则:不要去设计当前用不到的功能,不要去编写当前用不到的代码
例如:
1.系统暂时使用redis来存储配置信息,以后可能会用到zookeeper,我们就没有必要提前写zookeeper代码,但是我们还是要预留好扩展点,等到需要的时候,再去实现这部分代码
2.我们在项目中不要提前引入不需要依赖的开发包
#KISS和YAGNI区别
KISS是“如何做”的问题(尽量保持简单),而YAGNI是“要不要做”的问题(当前不需要就不去做)
# 针对什么时候重复造轮子,什么时候应该使用现成的工具类,开源框架?
# 什么时候重复造轮子
1.想学习轮子的原理
2.现有的轮子不能满足性能的需求,也没有好的替代品(因为轮子一般会考虑大而全的问题,这对性能有损耗)
3.小而简单的问题
# 什么时候应该使用现成的工具类,开源框架
对于工作中,应该使用工具类和开源框架,没有必要去自己造轮子,增加了开发成本,增加了开发周期