1、开闭原则
开闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
用抽象构建框架,用实现扩展细节。
优点: 提高软件系统的可复用性及可维护性。
不要改你以前写的代码,你应该加一些代码去扩展原来的功能,来实现新的需求。好处很好理解,改原来的代码很容易影响原来的功能,特别是接手别人的代码,不理解原先业务场景的情况下。
新添加校验规则的话,尽量不要修改以前的校验规则。可以回引入新的风险。
具体代码实现:
image
举例: 比如有一个课程抽象类,该课程包含课程id,名称,价格。同时开放的课程有java课程,C语言课程。上面的UML图就是两者之间的关系。
如果在此基础上需要添加优惠课程。那么我们有两种实现方式。一种就是直接在抽象接口中声明一个优惠获得价格的方法。另一种就是在java课程中直接修改获取价格的方法。
但是这两种方式都会存在问题。前者的问题在于修改的地方太多,接口是不应该经常变化的,尤其是在有很多实现类的前提下,这样修改会让我们修改很多实现类。后者的问题在于我们就无法获取原来的价格。不符合需求。
那么究竟怎样是合理的呢?
如果只是java课程打折,那么我们最好使用继承的方式,对原来的java课程进行扩展。
image
尽量不对底层的方法进行修改,防止风险的扩散。越高层的影响越小,提高了复用性。
2、依赖倒置原则
定义:高层模块不应该依赖底层模块,两者都应该依赖其抽象。
抽象不应该依赖细节;细节应该依赖抽象。
针对接口编程,不要针对实现编程。
优点: 可以减少类间的耦合性,提高系统稳定性,提高代码可读性和可维护性,可降低修改程序造成的风险。
image
上图是典型的面向实现编程,一旦我们需要添加新的课程就需要修改该类,改动是很频繁的。这里我们引入抽象。然后实现接口的方式。在应用层通过参数注入的方式调用具体实现类。
image
当然这样会有一个问题,我们在Mical类中只能注入一次ICourse的实例。那么为了多次调用,需要将ICourse暴露出去。
image
3、单一职责原则
单一职责原则:不要存在多于一个导致类变更的原因。
一个类、接口、方法只负责一项职责。
优点: 降低类的负责度,提高类的可读性,提高系统的可维护性,降低变更引起的风险。
例如声明一个鸟类,方法为行动的方式
public class Bird { public void mainMoveMode(String birdName) { System.out.println(birdName+ "用翅膀飞"); } }
在实际调用时,属于鸟类的有大雁,企鹅,鸵鸟。那么其实企鹅和鸵鸟其实不是使用翅膀的,这就出现了需求不一致的问题。我们一般的做法就是根据传入的参数判断,然后输出不同的结果。
public class Bird { public void mainMoveMode(String birdName) { if (birdName.equals("鸵鸟")){ System.out.println(birdName + "用脚走"); }else { System.out.println(birdName + "用翅膀飞"); } } }
这样会更改实现的具体流程,不留神就改错了。尤其是对于边界条件比较复杂的处理流程而言。
image
再比如课程类
public interface Course { //获取课程信息 String getCourseName(); byte[] getCourseVideo(); //课程管理 void studyCourse(); void refundCourse(); }
在这个类中,我们可以看到获取课程信息和退单是相互影响的。
那么我们可以将其进行拆分。
image
4、接口隔离原则
用多个专门的接口,而不是单一的总接口,客户端不应该依赖它不需要的接口。
一个类对一个类的依赖应该建立在最小的接口上。
建立单一的接口,不要建立庞大臃肿的接口。
尽量细化接口,接口中的方法尽量少。
优点: 符合我们常说的高内聚低耦合的设计思想,从而使得类具有很好的可读性,可扩展性和可维护性。
image
比如狗是不会飞的,那么我们要实现这个接口,就必须将它的fly方法不予实现。造成了浪费。
我们可以适度的将接口拆分,但不可过于细
image
5、迪米特法则
一个对象应该对其他对象保持最少的了解。又叫做最少知道原则。
尽量降低类与类之间的耦合。
强调只和朋友交流,不和陌生人说话。
朋友: 出现在成员变量,方法的输入,输入参数重的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。
优点: 降低类之间的耦合。
防疫站需要每个宠物的信息,这时候需要一个EpidemicStation对象。如何获取宠物信息有2种方式
通过宠物的主人,EpidemicStation早已有了Host对象,而Host中有一系列关于宠物(Animal)的方法
直接new一个宠物的对象,获取相关信息。
我们肯定直接会选择第一种。。。直觉是可以少建个对象。。。早说了我就是这种偷懒的程序猿。。。然而莫名的契合了迪米特法则。
Host只需要对Animal对象保持关注,而EpidemicStation只需要对Host对象保持关注。EpidemicStation对象不需要了解Animal。感觉直接点就是,在一个类中应该少出现其他类,其实这也是解耦的需要。
6、里氏替换原则
该原则是在1988年,由麻省理工学院的以为姓里的女士提出的。
如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
由定义可知,在使用继承时,遵循里氏替换原则,在子类中尽量不要重写和重载父类的方法。
继承包含这样一层含义:父类中凡是已经实现好的方法(相对抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。
继承作为面向对象三大特性之一,在给程序设计带来巨大遍历的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障。
public interface ICourse { Integer getId(); String getName(); Double getPrice(); }
public class JavaCourse implements ICourse { private Integer id; private String name; private Double price; public JavaCourse( Integer id,String name, Double price) { this.id = id; this.name = name; this.price = price; } @Override public Integer getId() { return this.id; } @Override public String getName() { return this.name; } @Override public Double getPrice() { return this.price; } @Override public String toString() { return "JavaCourse{" + "id=" + id + ", name='" + name + '\'' + ", price=" + price + '}'; } }
public class JavaDiscountCourse extends JavaCourse { public JavaDiscountCourse(Integer id, String name, Double price) { super(id, name, price); } public Double getDisCountPrice(){ return super.getPrice()* 0.8; } @Override public Double getPrice() { return super.getPrice() ; } }
public static void main(String[] args) { ICourse javaCourse = new JavaDiscountCourse(95,"Java架构设计",128.0); System.out.println( "课程Id:"+javaCourse.getId() + "课程名称:"+ javaCourse.getName() + "课程原格:"+ javaCourse.getPrice() + "课程优惠价格:"+ ((JavaDiscountCourse) javaCourse).getDisCountPrice() ); }
作者:AKyS佐毅
链接:https://www.jianshu.com/p/c460295f9964