前言
仅供学习和参考,文章内容来自相关书籍和搜索引擎以及一些个人的思考和心得。如有纰漏,欢迎指正。
七大原则
① 开闭原则
② 依赖倒置原则
③ 单一职责原则
④ 接口隔离原则
⑤ 迪米特法则
⑥ 里氏替换原则
⑦ 合成复用原则
开闭原则
开闭原则(Open-Closed Principle),对于一个软件实体,应该对拓展开放,对修改关闭。对修改关闭即保证了软件的一个完整性,保留了当前版本的基本功能,在做版本更迭的时候也方便向下兼容。试想如果我们每次更新都直接对代码进行修改,那么需要新功能的用户会进行软件更新,但是那些不需要新功能的呢?他们也必须更新代码,不然就会出问题。所以,在保证了对修改关闭的同时,也必须对拓展开放,拓展性的是每个系统必要条件,因为你永远不知道客户会有什么需求 。
作用
1、通过扩展已有软件系统,可以提供新的行为,以满足对软件新的需求,提高了软件系统的适应性和灵活性
2、已有的软件模块,特别是重要的抽象层模块不能再修改,提高了软件系统的一定的稳定性和延续性
3、这样的设计同时也满足了可复用性和可维护性
在实际开发中,如何去实现开闭原则呢?
实现开闭原则的关键是抽象
1、横向拓展
世界上的高级物种只有人类
public interface Human {
String getName();
String getSexLike();
String getSex();
}
此时,然后女娲首先捏了一个男娃儿
public class man implements Human{
private String name;
private String sexLike;
private String sex;
@Override
public String getName() {
return name;
}
@Override
public String getSexLike() {
return sexLike;
}
@Override
public String getSex() {
return sex;
}
}
然后女娲又捏了一个女娃儿,此时就可以进行横向拓展,而不必修改man中的getSex()的方法让他return "女"。因为man中的getSex()方法可能被其他方法调用。
public class woman implements Human{
private String name;
private String sexLike;
private String sex;
@Override
public String getName() {
return name;
}
@Override
public String getSexLike() {
return sexLike;
}
@Override
public String getSex() {
return sex;
}
}
2、纵向拓展
此时,女娲又捏了一个男娃儿,但是他喜欢男人。此时可以进行纵向拓展
public class gay extends man{
@Override
public String getSexLike() {
return "男";
}
}
依赖倒置原则
依赖倒置原则(Dependency Inversion Principle)是指高层模块不应该依赖底层模块,两者都应该依赖抽象,抽象不应该依赖细节,细节应该依赖抽象。其中:
高层模块:调用者
底层模块:被调用者
抽象:即是抽象类或者接口,两者是不能够实例化的
细节:即是具体的实现,实现接口或者继承抽象类的类
其实该原则更像是对开闭原则的一种具体实现,如果高层模块直接依赖具体实现,那么每一次修改的时候就必须修改具体实现类(耦合度太高),这就违背了开闭原则。其实依赖倒置原则在我看来更像现在得房屋租赁。大多数房东不能够直接去找到租房人员(高层模块不应该依赖底层模块),大多数租房人员也不容易找到房东,两者都需要通过中介进行交易(两者都应该依赖抽象),中介租房不需要管房子是什么样子的,只需要将它租出去(抽象不应该依赖细节),但租房人员有需求,必须找到中介(细节应该依赖抽象)。
但是我有一点不明白,这个原则为什么叫依赖倒置原则呢?"倒置",不应该是倒过来么?这更像是"中间商原则"。
作用
采用依赖倒置原则可以降低模块之间的耦合性,提高系统的稳定性,减少并行开发的风险,提高代码的可读性和可维护性
在实际开发中,如何去实现依赖倒置原则呢?
在我上述租房例子中,房东就是高层模块,租户就是底层模块,中介就是抽象,房子的类型就是细节
高层模块在开发中就是main()方法,因为它是程序的入口,负责调用其他。
1、不使用依赖倒置原则
底层模块——租户
public class Renter {
public void rentOneRoom(){
System.out.println("租户租到1室");
}
public void rentOneBadRoom(){
System.out.println("租户租到1室1厅");
}
}
高层模块——房东
public class Landlord {
public static void main(String[] args) {
Renter renter = new Renter();
renter.rentOneRoom();
renter.rentOneBadRoom();
}
}
租户租到1室
租户租到1室1厅
如果此时租户还想租两室一厅呢?我们又需要去Renter里添加租两室一厅的方法,在landlord里增加两室一厅的调用方法。这样频繁的添加代码,会有一些风险,影响系统的稳定性。
2、使用依赖倒置原则
定义抽象——中介
public interface Agent {
void rent();
}
定义细节——一室一厅、一室、两室一厅(创建3个java文件,此处图方便,代码贴在一起)
public class OneBadRoom implements Agent{
@Override
public void rent() {
System.out.println("租户租到1室1厅");
}
}
public class OneRoom implements Agent{
@Override
public void rent() {
System.out.println("租户租到1室");
}
}
public class TwoBadRoom implements Agent{
@Override
public void rent() {
System.out.println("租户租到2室1厅");
}
}
定义底层模块——租户
public class Renter {
public void rent(Agent agent){
agent.rent();
}
}
定义高层模块——房东
public class Landlord {
public static void main(String[] args) {
Renter renter = new Renter();
renter.rent(new OneRoom());
renter.rent(new OneBadRoom());
renter.rent(new TwoBadRoom());
}
}
租户租到1室
租户租到1室1厅
租户租到2室1厅
这样以后租户想租新的房子只需要增加细节(房子的类型)即可。而不需要修改底层代码。这种方式就叫做依赖注入。
其他依赖注入方式还有
构造器方式
修改租户Renter
public class Renter {
private Agent agent;
public Renter(Agent agent){
this.agent = agent;
}
public void rent(){
agent.rent();
}
}
修改房东Loadlord
public class Landlord {
public static void main(String[] args) {
Renter renter = new Renter(new OneRoom());
renter.rent();
}
}
租户租到1室
setter方式
修改租户Renter
public class Renter {
private Agent agent;
public void setAgent(Agent agent){
this.agent = agent;
}
public void rent(){
agent.rent();
}
}
修改房东Loadlord
public class Landlord {
public static void main(String[] args) {
Renter renter = new Renter();
renter.setAgent(new OneRoom());
renter.rent();
}
}
租户租到1室
单一职责原则
单一职责原则(Simple Responsibility Principle),顾名思义,就是专人干专事。反过来想,如果系统出了问题,有多种原因要去改一个类,那就说明这个类有多种职责。而且单一职责并不是单一功能。
作用
将功能分类,模块划分明确,修改一个模块不会造成其他模块的修改,降低模块之间的耦合度
比如查找一个商品信息,前台查找商品信息用于展示,后台查询商品信息用于管理。但是如下代码既承担了前台查询的职责,又承担了后台查询的职责。那么前台需要展示的信息需要改变时,会对后台也产生影响
1、不遵循单一职责原则
@Repository
public class Repository {
@Select("<script>" +
"select feild1,field2,field3 from table")
List<XXX> getSomeThing();
}
2、遵循单一职责原则
@Repository
public class Repository {
@Select("<script>" +
"select feild1,field2,field3 from table")
List<XXX> getSomeThingForProtal();
@Select("<script>" +
"select feild1,field4,field5 from table")
List<XXX> getSomeThingForBackend();
}
创建两个方法分别服务前台后台,这就满足了粗粒度的单一职责原则。
我们常说的高内聚低耦合原则其实就是单一职责的具体体现。单一职责原则是最简单也非常难实现的原则
接口隔离原则
接口隔离原则(interface Segregation Principle)是指用多个专门的接口,而不使用单一的总接口,客户端不应该只依赖它不需要的接口。这个原则是在接口设计时的规范:
1、一个类对一类的依赖应该建立在最小的接口之上。
2、建立单一接口,不要建立庞大臃肿的接口。
3、尽量细化接口,接口中的方法尽量少(不是越少越好,一定要适度)。
作用
防止庞大,臃肿的接口,避免接口污染,提高程序设计要求的细致划分性。降低大面积维护成本,一旦出现接口污染,会造成实现类中存在大量的不相关不需要去重写的方法
接口隔离原则符合我们常说的高内聚低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性。我们在设计接口的时候,要多花时间去思考,要考虑业务模型,包括以后有可能发生变更的地方还要做一些预判。所以,对于抽象,对业务模型的理解是非常重要的。
1、不遵循接口隔离原则
定义一个Animal类
public interface Animal {
void eat();
void fly();
void swim();
}
鸟类实现类
public class Bird implements Animal{
@Override
public void eat() {}
@Override
public void fly() {}
@Override
public void swim() {}
}
狗类实现类
public class Dog implements Animal{
@Override
public void eat() {}
@Override
public void fly() {}
@Override
public void swim() {}
}
可以看出,Bird 的 swim()方法可能只能空着,Dog 的 fly()方法显然不可能的。
2、遵循接口隔离原则
这时候,我们针对不同动物行为来设计不同的接口,分别设计 IEatAnimal,IFlyAnimal 和ISwimAnimal 接口
public interface EatAnimal {
void eat();
}
public interface FlyAnimal {
void fly();
}
public interface SwimAnimal {
void swim();
}
Dog 只实现 IEatAnimal 和 ISwimAnimal 接口
public class Dog implements EatAnimal, SwimAnimal{
@Override
public void eat() {}
@Override
public void swim() {}
}
这样拆分下来,就清晰很多。接口隔离原则也是高内聚低耦合的思想,那么它跟接口单一职责有什么区别呢?
单一职责和接口隔离辨析
1、单一职责原则侧重职责,接口隔离侧重对接口的依赖的隔离
2、单一职责原则侧重约束类,其次是接口,针对程序中实现的细节
3、接口隔离原则侧重约束接口,主要针对抽象需求,针对程序的整体框架的构建