设计模式的基础就是几种设计原则,其中就包含SOLID,KISS,YANGNI,DRY,LOD。这些原则可以说是框架和代码设计的经验,在代码开始写之前对这几种设计原则进行思考设计,能让我们的代码变的更加优美。所以虽然这些都是理论层面的东西,深入学习并思考也非常重要。
SOLID原则为五种原则的总称:
- S:单一职责原则 SRP Single Responsibility Principle
- O:开闭原则 OCP Open Closed Principle
- L:里氏替换原则 LSP Liskov Substitution Principle
- I:接口隔离原则 ISP Interface Segregation Principle
- D:依赖反转原则 DIP Dependency Inversion Principle
这里我们通过理论和实例对SOLI四种设计原则进行学习,D依赖反转后面单独进行学习。
1.SOLI设计原则
S:单一职责原则
这个原则就比较简单了,我们Java以及其他面向对象编程语言都是基于对象进行编程的,那么单一职责对应的就是每个类或者函数都有各自的职责,并且是单一的,也就是一个类或者一个函数不能承担过多的职责,不然就形成高耦合的代码了。
O:开闭原则
开闭原则主要是对拓展开放,修改关闭,这个其实也是对我们代码的拓展性进行定义,最简单的,我们开放一个接口给调用方,如果我们每次稍微要添加一点小功能,就要对参数等进行修改,那么调用方也要跟着改很多代码。这就违背了拓展性,遵循拓展开放,修改关闭的原则,可以让我们在添加功能的时候不用过多的修改已有的代码,也可以让上下游的调用方能更方便的对接
L:里氏替换原则
里氏替换原则的定义是子类对象能够替换程序中父类对象出现的任何地方,并保证原来程序的逻辑行为不变及正确性不被破坏,这个原则其实是对多态的进一步限制。比如我们现在父类一个函数是对积分排序,子类在继承的时候改成了对时间排序,这在多态上是没问题的,子类可以重写父类的函数,但是违背了里氏替换原则,而这在后面的编码中也会带来很多不方便的地方,我们要对对象判断是调用父类还是子类的函数,这个就会让开发工作带来更复杂的场景。也会让代码变的更复杂,这显然是我们不想看到的。
I:接口隔离原则
接口隔离原则的定义是客户端不应该强迫依赖他不需要的接口。其实更直观的来理解,跟单一职责原则其实有点像,比如一个接口A中我们定义了B,C两个方法,这个时候我们有三个类实现了这个接口,这个时候其中一个类需要加D方法,那么这个时候我们就不能在D上添加,因为其他两个类是不需要D方法的,如果在A中添加D方法,就给另外两个方法添加了不必要的接口函数。所以这种情况最好的办法就是另外创建一个接口,添加D方法,只让其中一个类实现新接口,这样可以做到接口隔离,也能让每个类只实现他需要的接口。这样依赖每个接口的职责就分工明确了,所以他其实跟单一职责原则有点相似。
2.实例:配置文件更新需求
对于上面的四种设计原则,我们通过一个简单的例子来深入的学习和使用一下。
比如我们现在系统中,用到了Redis,并且我们需要对Redis的配置文件进行实现。那么基本的代码如下
package DesignPrinciple.SOLI;
/**
* @time: 2022/10/20
* @author: yuanyongan
* @description: 对redis的配置文件进行操作
*/
public class RedisConfig {
private String configSource; // 配置中心(比如Zookeeper,Nacos等)用String简化
private String address; // redis服务器地址
private int timeout; // 连接超时时间
private int maxTotal;
public RedisConfig(String configSource){
this.configSource = configSource;
}
public String getAddress(){
return this.address;
}
public void update(){
// 从配置中心加载其他属性
}
// ...省略其他方法
}
上面的RedisConfig就是对redis的配置文件进行操作的类,那么现在有个问题,我们现在的配置文件肯定不止Redis,如果我们有了MySQL和Kafka,怎么样去调整呢。
为了实现单一职责原则,我们选择针对每个中间件都单独创建一个新的类。
public class KafkaConfig{ // ...}
public class MysqlConfig{ // ...}
需求1: 现在我们要加入一个新需求,那就是当我们配置中心的配置发生改变的时候,需要对Redis和Kafka的配置进行及时修改,但MySQL不需要。
所以我们需要创建一个接口
package DesignPrinciple.SOLI;
public interface HotUpdater {
void hotUpdate();
}
**需求2:**这个时候我们又有一个新需求,对于Redis和MySQL的配置,我们需要对配置有一个输出的方法。
这个时候,为了接口隔离原则,我们选择新建一个Viewer接口
package DesignPrinciple.SOLI;
import java.util.Map;
public interface Viewer {
String outputInPlainText();
Map<String, String> output();
}
那么三个配置类就成了这样
public class RedisConfig implements HotUpdater, Viewer
public class MysqlConfig implements Viewer
public class KafkaConfig implements HotUpdater
然后让RedisConfig和MySQL实现Viewer接口,这样依赖,就达到了接口隔离原则。并且这样一来,各个模块之间耦合性也非常低,支持我们对后续的更多的功能进行便捷的开发。
当然,我们也可以将Viewer和Updater合并成一个Config接口,然后让三个配置类统一实现Config,但是这样一来最大的问题就是,MySQL其实不需要热更新,Kafka不需要输出配置文件,这样的话就违背了接口隔离原则,给不需要的类强加了接口函数去实现。
当然,设计原则只是帮助我们将代码框架设计的等清晰,易于拓展,真正的使用还是要针对实际的业务场景进行取舍的,毕竟复杂的业务场景完全遵守所有设计原则也是不可能的,上面的例子也是简单的学习一下,有不足的地方还欢迎大家提出来进行交流。
代码都放在个人git仓库中了,欢迎交流