软件架构设计原则
开闭原则
开闭原则(Open-Closed Principle,OCP)是指一个软件实体,应该对扩展的开放的,对于修改是关闭的。开闭就是指拓展与修改两个行为。什么意思呢?就是说对于一个Java对象来讲,你可以去继承它方法和属性,对继承类进行拓展,但是不可以直接修改它的方法和属性。这样提高了软件系统的可复用性和可维护性。
开闭原则作为面向对象设计的基本原则,直接对接了面向对象的三大特性,继承、封装、多态。可以在不修改源代码的情况下实现新增功能。
简单的举个例子,宠物 Pets,有名字、年龄大小、毛色等属性
public class Pets{private String name;private Integer age;private String color;
}
整个宠物圈,可能有小猫、小狗、小猪等等一些宠物。例如下面来建立一个一个猫的宠物类型,它除了上面的属性之外,还有一个动作就是吃。这个时候可以继承Pets类然后给小猫一个吃的方法。
public class CatPets extends Pets{public void eat(){}
}
对于小狗、小猪也是以同样的方式创建,这个时候我们可以知道,猫也有很多的更细的分类。这个时候我们对于每个猫的修改就是继承CatPets进行更加细致的分类了。而不是去修改顶层的Pets类型。
依赖倒置原则
依赖倒置原则(Dependence Inversion Principle,DIP)是指在设计代码结构的时候,高层次的模块不应该依赖低层次的模块,二者都应该依赖其抽象。抽象内容不应该依赖具体的细节性的内容。通过依赖倒置,可以减少类对象与类对象之间的耦合性。这样可以提高系统的稳定性,提高代码的可读性和可维护性,并且能够降低修改程序所造成的后续风险。
拿上面的宠物的例子来讲,先创建一个人的对象Person类
public class Person{// 养猫public void keepCarPets(){}// 养狗public void keepDogPets(){}
}
这个人非常喜欢养宠物,他养了猫和狗两种宠物。但是如果这个时候他看到一个宠物猪也比较好,他想养宠物猪怎么去做呢?就需要在Person类中增加 keepPigPets()的方法。这样的做法就是修改了高层次的代码,如果这样修改的话每次他想养新的宠物的时候,就要去新增一个方法,如果修改的内容较多的话,就会是一个非常复杂的工程了。
这个时候就可以通过下面这种方式来实现了
public interface KeepPets{public void keeyPets(Pets pets){}
}
定义一个接口类,然后这个人继承这个接口类,接口类中有一个方法就是keepPets(Pets pets)养宠物,这个时候,如果想去养新的宠物的时候就可以通过如下的方式实现了
public class Person interface KeepPets{public void keeyPets(Pets pets){}
}
public class Test{public static void main(String[] args){Person person = new Person();person.keeyPets(new CatPets());person.keeyPets(new DogPets());person.keeyPets(new PigPets());}}
这个时候就可以看到,无论想养多少宠物的时候都不会再害怕了。只需要告诉养什么就可以了,而不需要修改底层的代码。实际上这就是依赖注入。注入的方式还有很多,通过构造函数注入,通过Setter方法注入等方式。
通过构造器注入的时候,在调用的期间每次都会创建新的实例,如果是一个全局单实例的对象话就只能通过Setter方式进行注入。
切记:以抽象为基准比以细节为基准搭建起来的架构要稳定很多,因此在拿到需求之后一定要面向接口进行编程,先设计顶层再设计细节。
单一职责原则
单一职责(Simple Responsibility Pinciple,SRP)是指不要存在多余一个导致类变更的原因。什么意思呢?假设我们又一个类负责的是两个功能,一旦发生需求变化的时候,修改其中一个功能就会导致另一个功能也发生了故障。这样一来,一个类就存在了两个导致类变更的原因。那么如何解决这个问题呢?解耦。后期需求变更之后相互之后不会产生影响。这样的设计方式可以大大降低类的复杂度,提高可读性,提高系统的可维护性,降低了变更代码之后引起的风险。总体来说就是一个类只负责一个功能。
接口隔离原则
接口隔离原则(Interface Segregation Principle,ISP)是指用多个专门的接口,而不是单一的使用一个接口。客户端不应该依赖他不需要的接口。在实现这个设计原则的时候需要注意以下的几点。
- 一个类对另一个类的依赖应该建立在最小的接口上
- 建立一个单一的接口,不需要一个庞大的总接口
- 尽量让接口细化,减少接口中的方法,但并不是越少越好。
接口隔离原则符合常说的高内聚、低耦合的设计思想。可以让类具有更好的可读性、可扩展性和可维护性。在设计接口的时候需要花点时间去思考,需要的业务模型,包括以后可能发生的变化的预判。
迪米特原则
迪米特原则(Law of Demeter LoD)是指一个对象应该对其他对象保持最小的了解。所以又叫最少知道原则。尽量降低类与类之间的耦合度。该原则主要强调:只和自己的朋友交流,不要和陌生人说话。出现在成员变量、方法和输入、输出参数中的类都可以称为朋友类,而出现在方法体内部的类则不属于朋友类。
例如,你作为一个学校的校长,你想要知道某个班的学生有多少人,你不需要亲自去数,而是只需要找到对应班级的班主任即可。如下
有一个学生类Student
public class Student{}
有一个班主任类
public class HeadMaster{public Integer countStudent(List<Student> studentList){return classStudent.size();}
}
一个校长类
public class SchoolMaster{public Integer studentNumber(HeadMaster headMaster){List<Student> studentList = new ArrayList<>();headMaster.countStudent(studentList);}
}
这个时候就实现了,校长与老师的关联,校长并不需要直接与每个学生发生关联。
里式替换原则
里式替换原则(Liskov Substitution Principle,LSP)是指如果每个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有的对象O1都替换成O2的时候,程序P的行为没有发生变化,那么类型T2就可以直接转换为T1。
这个定义看上去有点抽象,简单的理解为,一个软件实体如果适用于一个父类,那么一定使用与其子类,所有的引用了父类的地方必须能够透明的使用其子类对象,子类对象能够替代父类对象,但是程序的逻辑不变。可以简单的理解为子类可以继承父类的功能,但不能改变父类原有的功能。这也就是很多的地方会有一个注解@Override 的原因。
使用里式替换原则有如下的有点
- 1、约束继承泛滥,是开闭原则的一种体现
- 2、加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的可维护性,降低了需求变更的时候引入的风险。
合成复用原则
合成复用原则(Composite/Aggregate Reuse Principle,CARP)是指在开发过程中尽量使用对象组合、聚合而不是通过继承关系达到可复用的目的。这样可以使得系统更加灵活,降低类与类之间的耦合度,一个类的变化不会对其他类产生影响。
继承被称为白箱复用,相当于把所有父类的细节都暴露给子类,组合的方式则被称为黑箱复用。无法获取到类以外的对象的实现细节。但是这一设计需要严格遵守OOP模型。
总结
能将设计原则结合到自己的项目中,可以极大的提升代码的复用率,降低系统耦合度,提高程序拓展性,为后续的升级迭代打下坚实的基础。