目录
一:开闭原则
二:依赖倒置原则
三:单一职责原则
四:接口隔离原则。
五:迪米特法则
六:里氏替换原则
七:合成复用原则
设计原则总结:
一:开闭原则
开闭原则(Open-Closed Principle,OCP),指一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。强调的是用抽象构建框架用实现扩展细节,可以提高软件的可复用性以及可维护性。开闭原则可以指导我们不修改源码,但是可以增加新功能。
比如我们的service层,dao层用接口来实现,用实现类来扩展功能。
二:依赖倒置原则
依赖倒置原则(Dependence Inversion Principle,DIP)指设计代码结构时,高层模块不应该依赖底层模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。通过依赖倒置可以降低类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并降低修改程序带来的风险。
比如有一个Tom类,Tom学习Java,Python课程。
public class Tom {
public void studyJavaCourse(){
System.out.println("tom 学习Java课程");
}
public void studyPythonCourse(){
System.out.println("tom 学习Python课程");
}
}
在使用的时候如下:
public class CourseMain {
public static void main(String[] args) {
Tom tom = new Tom();
tom.studyJavaCourse();
tom.studyPythonCourse();
}
}
如果后面Tom需要学习新的课程,就需要修改Tom的源码继续增加其它课程的方法。代码要从底层到高层(调用层)依次修改。如此一来系统发布以后维护很不稳定。
因此要把这个功能分成抽象接口来实现。
首先有个课程的接口ICourse
public interface ICourse {
void study();
}
Java课程单独实现一个类。
public class JavaCourse implements ICourse {
@Override
public void study() {
System.out.println("学习Java");
}
}
Python课程单独实现一个类。
public class PythonCourse implements ICourse {
@Override
public void study() {
System.out.println("学习Python");
}
}
对于Tom类,依赖课程的抽象ICourse.
public class Tom {
public void study(ICourse course){
System.out.println("tom 正在");
course.study();
}
}
调用的时候:
public class CourseMain {
public static void main(String[] args) {
Tom tom = new Tom();
tom.study(new JavaCourse());
tom.study(new PythonCourse());
}
}
这个时候看代码,无论Tom再想学什么课程,只要新增课程的实现类,再高层调用的时候传入就行了,而不需要修改底层的代码。实际上这是一中非常熟悉的方式,依赖注入,注入的方式还有构造器方法注入和Setter方法注入,只是向Tom传递ICourse实现类的方式不一样而且。
注意:
以抽象为基准比以细节为基准搭建起来的架构要稳定的多,因此大家在拿到需求后要面向接口编程,按照先顶层再细节的顺序设计代码结构。
三:单一职责原则
单一职责原则(Simple Responsibility Principle)SRP,指不要存在一个以上导致类变更的原因,假设一个类负责两个功能,一旦需求发生变化,修改其中一个职责的逻辑代码,有可能会导致另一个职责的功能发生故障。因此我们需要用两个类进行实现两个不同的职责功能,进行解耦。总体来说,一个Class,Interface,Method只负责一项职责。
这个比较好理解,就不举例子了。
四:接口隔离原则。
接口隔离原则(Interface Segregation Principle ,ISP)指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。因此设计接口时应当注意一下几点:
(1)一个类对另外一个类的依赖应该建立在最小接口上。
(2)建立单一接口,不要建立庞大臃肿的接口。
(3)尽量细化接口,接口中方法尽量少(也不是越少越好,要适度)
接口隔离原则符合"高内聚 低耦合"的设计思想,使得类具有很好的可读性,可扩展性和可维护性。在接口设计的时候要多花时间思考,要考虑业务模型,包括还要对以后可能发生变更的地方做一些预判。所以,在实际开发中,我们对抽象,业务模型的理解是非常重要的。
假如有个动物的顶级接口:
public interface IAnimal {
void eate();
void fly();
void swim();
}
实现一只狗:
package com.my.ioc.pojo.design;
public class Dog implements IAnimal {
@Override
public void eate() {
System.out.println("dog 吃东西");
}
@Override
public void fly() {
}
@Override
public void swim() {
}
}
实现一只鸟:
package com.my.ioc.pojo.design;
public class Bird implements IAnimal {
@Override
public void eate() {
}
@Override
public void fly() {
System.out.println("bird fly");
}
@Override
public void swim() {
}
}
由以上两个实现类看出Dog类中的fly方法只能空着,Bird中的swim方法也只能空着,这个时候就需要针对不同的动物行为来设计不同的接口,分别设计IEateAnimal,ISwimAnimal,IFlyAnimal接口。
public interface IEateAnimal {
void eate();
}
public interface IFlyAnimal {
void fly();
}
public interface ISwimAnimal {
void swim();
}
针对不同的动物有不同的行为来实现不同的接口。
public class DogNew implements IEateAnimal,ISwimAnimal {
@Override
public void eate() {
System.out.println("dog 要吃东西");
}
@Override
public void swim() {
System.out.println("dog 其实是可以游泳的");
}
}
public class BirdNew implements IFlyAnimal,IEateAnimal {
@Override
public void eate() {
System.out.println("bird 吃东西");
}
@Override
public void fly() {
System.out.println("bird 要飞起来");
}
}
五:迪米特法则
迪米特法则(Law of Demeter ,LoD)又叫最少知道法则,指一个对象应该对其它对象保持最少的了解,尽量降低类与类之间的耦合。
它主要强调只和朋友交流,不和陌生人说话。出现在成员变量,方法的输入输出参数中的类都可以称为成员朋友,而出现在方法体内部的类不属于朋友类。
迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。
比如:我们经常使用的三层架构,controller层中调用service层,在service层中调用dao层,在dao层中访问数据库,而不是跳过中间层直接controller访问dao即使service层有时候并不处理任何业务逻辑,只起到友元类的作用。
六:里氏替换原则
里氏替换原则(Liskov Substitution Principle ,LSP)指如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有对象O1替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
定义可以理解为: 一个软件实体如果适用于一个父类,则一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。换句话说,子类可以扩展父类的功能,但不能改变父类原有的功能。根据这个理解,我们对其总结如下:
1)子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
2)子类可以增加自己特有的方法。
3)当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类方法更宽松。
4)当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的返回值要比父类的方法更加严格或相等。
用正方形,矩形,和四边形的关系说明里氏替换原则,我们都知道正方形是一个特殊的长方形,可以新建一个长方形父类Rectangle.
package com.my.ioc.pojo.design;
public class Rectangle {
private long heigth;
private long width;
public long getHeigth() {
return heigth;
}
public void setHeigth(long heigth) {
this.heigth = heigth;
}
public long getWidth() {
return width;
}
public void setWidth(long width) {
this.width = width;
}
}
创建正方形继承长方形:
package com.my.ioc.pojo.design;
public class Squar extends Rectangle {
private long length;
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
@Override
public long getHeigth() {
return getLength();
}
@Override
public void setHeigth(long heigth) {
setLength(heigth);
}
@Override
public long getWidth() {
return getLength();
}
@Override
public void setWidth(long width) {
setLength(width);
}
}
使用:
public class CourseMain {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setHeigth(10);
rectangle.setWidth(20);
resize(rectangle);
}
public static void resize(Rectangle rectangle){
while (rectangle.getWidth()>=rectangle.getHeigth()){
rectangle.setHeigth(rectangle.getHeigth()+1);
System.out.println("width:"+rectangle.getWidth()+"heigth"+rectangle.getHeigth());
}
System.out.println("end: width:"+rectangle.getWidth()+"heigth"+rectangle.getHeigth());
}
}
> Task :spring-myselftest:CourseMain.main()
width:20heigth11
width:20heigth12
width:20heigth13
width:20heigth14
width:20heigth15
width:20heigth16
width:20heigth17
width:20heigth18
width:20heigth19
width:20heigth20
width:20heigth21
end: width:20heigth21
由运行结果可知,高比宽还大,这在长方形中是不正常的。看下如果把长方形换成子类正方形看下结果:
public static void main(String[] args) {
Squar rectangle = new Squar();
rectangle.setHeigth(10);
rectangle.setWidth(20);
resize(rectangle);
}
结果运行是个死循环:
public static void main(String[] args) {
Squar rectangle = new Squar();
rectangle.setHeigth(10);
rectangle.setWidth(20);
resize(rectangle);
}
width:12190heigth12190
width:12191heigth12191
width:12192heigth12192
width:12193heigth12193
width:12194heigth12194
width:12195heigth12195
width:12196heigth12196
width:12197heigth12197
width:12198heigth12198
width:12199heigth12199
width:12200heigth12200
width:12201heigth12201
> Task :spring-myselftest:CourseMain.main() FAILED
在将父类替换为子类之后,程序运行结果没有达到预期,违背了里氏替换原则,因此代码设计处在一定的风险。里氏替换原则只存在于父类与子类之间,约束继承泛滥。
再来新建一个四边形接口-QuardRangle
public interface QuardRangle {
long getWidth();
long getHeigth();
}
public class Rectangle implements QuardRangle{
private long heigth;
private long width;
public long getHeigth() {
return heigth;
}
public void setHeigth(long heigth) {
this.heigth = heigth;
}
public long getWidth() {
return width;
}
public void setWidth(long width) {
this.width = width;
}
}
public class Squar implements QuardRangle {
private long length;
public void setLength(long length) {
this.length = length;
}
@Override
public long getWidth() {
return length;
}
@Override
public long getHeigth() {
return length;
}
}
如果此时把resize方法的参数换成Squar就会报错,Squar不再是Rectangle的子类,约束了继承。
七:合成复用原则
合成复用原则(Composite/Aggregate Reuse Principle,CARP)指尽量使用对象组合(has-a)或对象聚合(contanis-a)的方式实现代码复用,而不是通过继承关系达到代码复用的目的。降低类之间的耦合,使得一个类的变化对另外的类影响相对较小。
继承,又称为白箱复用,相当于把所有的实现细节暴露给子类。组合/聚合又称为黑盒复用对类以外的对象是无法获取实现细节的。
设计原则总结:
设计原则是设计模式的基础,在实际开发过程中不一定所有代码都遵循设计原则,但是我们需要考虑业务场景进行权衡代码的设计。
设计原则 | 归纳 | 目的 |
开闭原则 | 对扩展开放,对修改关闭 | 降低代码维护带来的新风险 |
依赖倒置原则 | 高层不应该依赖底层 | 更有利于代码结构的升级扩展 |
单一职责原则 | 一个类只做一件事 | 便于理解,提高代码可读性 |
接口隔离原则 | 一个接口只做一件事 | 功能解耦,高内聚,低耦合 |
迪米特法则 | 不该知道的不知道 | 只和朋友交流不和陌生人说话,减少代码臃肿 |
里氏替换原则 | 子类重写方法功能发生改变,不应该影响父类方法的含义 | 防止继承泛滥 |
合成复用原则 | 尽量使用组合代码复用,而不是继承 | 降低代码耦合 |