什么是SOLID原则
SOLID = SRP(职责单一原则) + OCP(对扩展开发,修改关闭原则)+ LSP(里氏替换原则)+ ISP(接口隔离原则)+ DIP(依赖反转原则)
SRP: Single Responsibility Principle,一个类或者模块只负责完成一个职责;
OCP:Open Closed Principle,软件实体(模块、类、方法等)应该“对扩展开发,对修改关闭”;
LSP:Liskov Substitution Principle,子对象能够替换程序中父类对象出现的任何地方,并且保证原来的程序逻辑行为不变及正确性不被破坏;
ISP: Interface Segregation Principle,客户端应该不强迫依赖它不需要的接口;
DIP:Dependency Inversion Principle,高层模块不要依赖底层模块,高层模块和底层模块之间应该通过抽象来相互依赖,除此之外,抽象不要依赖具体的实现细节,具体实现细节依赖抽象。
SOLID原则评判标准
SRP(职责单一原则)
评判标准
一个类或者模块只负责完成一个职责,不要设计大而全的类,要设计的粒度小、功能单一的类,也就是说一个类包含两个或者两个以上业务不相关的功能,就可以说它的职责不够单一。
类中的代码行数、函数或属性过多,影响代码的可读性和维护性,就需要考虑对类进行拆分;
类依赖其它类过多,或者依赖的类的其它类过多,不符合高内聚、低耦合的设计思想,就需要考虑对类就行拆分;
私有方法过多,考虑能否将私发方法到新的类中,设置为public方法,供更多的类使用,提高代码的复用性;
比较难给定一个适合的名字,很难用一个业务名词概括,说明职责定义的不够清晰;
类中大量的方法都是集中在操作类的几个属性,其它的属性就可以拆分出来。
代码示例
public classUser{
private String id;
private String userName;
private String pwd;
private String phone;
private String city;
private String province;
private String town;
private BigDecimal balance;
publicintaddUser(User user){
return 1;
}
publicintdeleteUser(String id){
return 1;
}
publicListqueryUserByRoleId(String id){
return new ArrayList<>();
}
}
User类对用户基本信息操作比较频繁,但是地址相关信息只在订单相关功能才用到city、province、town可以单独拆分成一个实体;
User基本操作基本不用到账号余额,放到这里有些多余;
addUser和deleteUser在一定场景下符合职责单一原则,但是业务扩展可能需要对删除做权限控制,就不太符合这个原则,没有一定的准则;
queryUserByRoleId虽然查询的是用户相关信息,但引用了其它实体的内容,我们可以认为是职责不单一。
OCP(对扩展开发,修改关闭原则)
评判标准
一段代码是否易于扩展,某段代码在应未来需求变化的时候,能够做到“对外开放,对修改关闭”,那就说明这段代码的扩展性比较好。
添加一个新功能,不可能任何模块、类、方法不做修改。类需要创建、组装、并且做一些初始化操作,才能构建可运行的程序,修改这部分代码是再所难免的。我们要做的经量修改的时间操作更集中、更少、更上层、经量让最核心、最复杂的那部分逻辑代码满足开闭原则。
代码示例
public classOcp{
publicboolean pkgValidate(String userName, String phone){
if(StringUtils.isEmpty(userName)){
return false;
}
if(phone.length() == 1){
return false;
}
return true;
}
}
如果我们需要新增一个报文校验,就会对这个接口的代码做修改,对应的单元测试全部需要改;
这块属于校验的核心功能,每次改还要把之前的功能测试一遍;
示例代码重构
public classOcpUser{
private String id;
private String userName;
private String pwd;
private String phone;
private BigDecimal balance;
//get set方法省略
}
-------------------------------------------------------------
public abstract classOcpHandler{
publicabstractbooleancheck(OcpUser ocpUser);
}
-------------------------------------------------------------
public classPhoneOcpHandlerextendsOcpHandler{
@Override
publicbooleancheck(OcpUser ocpUser){
if(ocpUser.getPhone().length() == 1){
return false;
}
return true;
}
}
-------------------------------------------------------------
public classUserNameOcpHandlerextendsOcpHandler{
@Override
publicbooleancheck(OcpUser ocpUser){
if(StringUtils.isEmpty(ocpUser.getUserName())){
return false;
}
return true;
}
}
-------------------------------------------------------------
public classOcpC{
private List ocpHandlers = new ArrayList<>();
publicvoidaddAlertHandler(OcpHandler ocpHandler){ this.ocpHandlers.add(ocpHandler); }
publicvoidcheck(OcpUser ocpUser){
for (OcpHandler handler : ocpHandlers) {
handler.check(ocpUser);
}
}
}
-------------------------------------------------------------
public classApplicationContext{
private OcpC ocpC;
// 饿汉式单例
private static final ApplicationContext instance = new ApplicationContext();
privateApplicationContext(){
instance.initializeBeans();
}
publicstaticApplicationContextgetInstance(){
return instance;
}
publicvoidinitializeBeans(){
ocpC.addAlertHandler(new PhoneOcpHandler());
ocpC.addAlertHandler(new UserNameOcpHandler());
}
publicOcpCgetOcpC(){
return ocpC;
}
}
-------------------------------------------------------------
public classDemo{
publicstaticvoidmain(String [] args){
OcpUser ocpUser = new OcpUser();
ocpUser.setUserName("张三");
ocpUser.setPhone("13288888888");
ApplicationContext.getInstance().getOcpC().check(ocpUser);
}
}
现在扩展需要改OcpUser新增一个属性,需要校验的属性;
添加一个handler实现check;
在初始化initializeBeans新增一个handler;
在调用的时候加入对应的属性。
我们不能做到完全的对内扩展开放、对修改关闭,我们只可能在最上面抽象尽可能不该动原逻辑的前提下做扩展。
LSP(里氏替换原则)
评判标准
里式替换原则和多态类似,但是多态是实现方式,是面向对象的一个特性,是一种编程语言的语法,原则是规范,规范你怎么开发。
子类违背父类声明要实现的功能,比如说我父类从小到大排序,子类重新这个方法后是从大到小的顺序排序;
子类违背父类对出入、输出、异常约定,入参和出参类型一样,抛的异常类型也必须完全一样;
子类违背父类注释中所罗列的任何特殊说明,实现方法很父类注释方式说明不符。
代码示例
public classParent{
publicvoidsort(List list){
//重小到大排序逻辑
}
publicvoidquery(String userName)throwsNullPointerException{
//查询逻辑
}
/**
* 加1操作
*@paramnum
*/
publicvoidadd(intnum){
num++;
}
}
public classLspextendsParent{
@Override
publicvoidsort(List list){
//重大道小排序逻辑
}
@Override
publicvoidquery(String userName)throwsNotBoundException{
//查询逻辑
}
/**
* 加1操作
*@paramnum
*/
@Override
publicvoidadd(intnum){
num--;
}
}
sort排序的逻辑完全很父类相反;
抛出异常类型不一致;
和注解标识做不相关操作;
ISP(接口隔离原则)
评判标准
接口可以理解成三类,一组API接口集合(业务具体功能点)、单个API接口或者函数(类中的某一个方法)、OOP中的接口(Inteface)概念。
设计职责更加单一的接口,单一就意味着通用、复用性好;
某些类在实现接口的时候,需要重写一些不需要的方法,做了一些无用功。
代码示例
public interfaceCommonService{
publicListqueryUserName(String status);
publicintdeleteUser(Long id);
publicListqueryRoleName(String userName);
publicintdeleteRole(Long id);
}
public classRoleImplimplementsCommonService{
@Override
publicListqueryUserName(String status){
return null;
}
@Override
publicintdeleteUser(Long id){
return 0;
}
@Override
publicListqueryRoleName(String userName){
return null;
}
@Override
publicintdeleteRole(Long id){
return 0;
}
}
public classUserImplimplementsCommonService{
@Override
publicListqueryUserName(String status){
return null;
}
@Override
publicintdeleteUser(Long id){
return 0;
}
@Override
publicListqueryRoleName(String userName){
return null;
}
@Override
publicintdeleteRole(Long id){
return 0;
}
}
接口不灵活,用户和角色的接口放在一起要多做一个工作量
复用性很差
不利于代码拆分、管理
DIP(依赖反转原则)
评判标准
依赖反转原则也可以叫依赖倒置原则,高优先级的类不应该强行依赖低优先级的类,而是通过一个抽象类间接的操作,子类也基于抽象类做实现。
代码是否易于拆分,在代码重构的时候很难对模块之间解耦,就说明依赖关系太死;
低层次模块提供的接口要足够的抽象、通用,在设计时需要考虑高层次模块的使用种类和场景;
高层次模块没有依赖低层次模块的具体实现,方便低层次模块的替换。
代码示例
interfaceIWorker{
publicvoidwork();
}
classWorkerimplementsIWorker{
publicvoidwork(){
// ... working
}
}
classSuperWorkerimplementsIWorker{
publicvoidwork(){
// ... working much more
}
}
classManager{
IWorker worker;
publicvoidsetWorker(IWorker w){
this.worker = w;
}
publicvoidmanage(){
this.worker.work();
}
}
Manager具体实现不依赖Worker和SuperWorker类;
通过抽象接口IWorker相互依赖;
具体实现类依赖接口IWorker;
个人网站:/
公众号: