4.接口隔离原则
- 接口隔离原则(Interface Segregation Principle,ISP)是指用多个专门的接口,而不是使用单一的总接口,客户端不要依赖自己不需要的接口。这个原则指导我们在设计时注意以下几点:
- 1、一个类对另一个类的依赖应该建立在最小的接口上
- 2、建立单一的接口,不要建立复杂臃肿的接口
- 3、尽量细化接口,接口内方法功能尽量不要重复
- 接口隔离原则符合我们常说的"高内聚、低耦合"的设计思想,从而提高了类的可扩展性、可维护性和可读性。所以我们在设计接口的时候,要多考虑业务模型和未来可能产生的情况,请看示例:
- 定义一个动物的接口
public interface IAnimal {
void eat();
void swimming();
}
- 定义一个GuangTouQiang,实现动物接口:
public class GuangTouQiang implements IAnimal {
@Override
public void eat() {
}
@Override
public void swimming() {
}
}
- 定义一个XiongDa类,实现动物接口:
public class XiongDa implements IAnimal {
@Override
public void eat() {
}
@Override
public void swimming() {
}
}
- 可以思考一下,光头强可是不会爬树的哦(管他会不会,说他不会他就不会{{{(>_<)}}}),那这个方法只能空着,冗余在这,这样的设计就太不合理了,我们不妨改下设计思路:分别设计IEatAnimal和ISwimmingAnimal两个接口,然后实现类分别实现需要的功能所在的接口就可以了:
- IEatAnimal接口
public interface IEatAnimal {
void eat();
}
- ISwimmingAnimal接口
public interface ISwimmingAnimal {
void swimming();
}
- 光头强只需要实现IEatAnimal 接口就可以了:
public class GuangTouQiang implements IEatAnimal {
@Override
public void eat() {
}
}
- 熊大实现俩接口:
public class XiongDa implements IEatAnimal, ISwimmingAnimal {
@Override
public void eat() {
}
@Override
public void swimming() {
}
}
5.迪米特法则
- 迪米特法则(Law of Demeter LoD)是指一个对象对其他的对象保持最少的了解,又叫最少知道原则(Least Knowledge Principle, LKP),尽量降低类与类之间的耦合。迪米特法则主要强调:只和朋友说话,不搭理陌生人。其中出现在成员变量、方法参数输入输出的类都可以称为朋友类,而出现在方法体内部的类都属于陌生人类。
- 有一天,吉吉国王安排熊大和熊二去采果子,准备过冬,到完成的时候,熊大和熊二向吉吉国王报告采了多少果子:
- 果子类:
public class Fruit {
}
- 熊大熊二类:
public class XiongDaXiongEr {
public void reportFruit(List<Fruit> fruitList) {
System.out.println("哥俩今天采的果子数量是:" + fruitList.size());
}
}
- 吉吉国王类:
public class JiJiKing {
public void checkFruit(XiongDaXiongEr brothers) {
//一个一个地检查水果
List<Fruit> fruitList = new ArrayList<Fruit>();
for(int i = 0; i < 30; i++) {
fruitList.add(new Fruit());
}
brothers.reportFruit(fruitList);
}
}
- 接下来看一下具体工作:
public class StartWork {
public static void main(String[] args) {
JiJiKing jiJiKing = new JiJiKing();
XiongDaXiongEr brothers = new XiongDaXiongEr();
jiJiKing.checkFruit(brothers);
}
}
- 这时,其实功能都已经实现,看着也没什么问题,根据迪米特法则,吉吉国王和果子类之间不需要产生直接的交流,而熊大熊二报告需要引用果子类,这时候只需要稍加改造就可以了:
- 熊大熊二类:
public class XiongDaXiongEr {
public void reportFruit() {
List<Fruit> fruitList = new ArrayList<>();
//一个一个地检查水果
for(int i = 0; i < 30; i++) {
fruitList.add(new Fruit());
}
System.out.println("哥俩今天采的果子数量是:" + fruitList.size());
}
}
- 吉吉国王类:
public class JiJiKing {
public void checkFruit(XiongDaXiongEr brothers) {
brothers.reportFruit();
}
}
这样,吉吉国王和水果之间就没有直接的依赖关系了。
6.里氏替换原则
- 里氏替换原则(Liskov Substitution Principle,LSP)是指如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1替换为o2时,程序P的行为没有变化,那么类型T2就是T1的子类型。好绕哦,其实简单点说就是:一个软件实体,如果适用父类的话,那么一定也适用其子类,所有引用父类的地方,必须透明地使用其子类的对象,子类可以替换父类的对象,而逻辑不变。
- 可以总结以下几点:
- 1、子类可以实现父类的抽象方法,而不能覆盖父类的非抽象方法
- 2、子类中可以增加自己特有的方法
- 3、当子类的方法重载父类的方法时,方法的前置条件(方法的输入/入参)要别比父类的输入参数更加宽松
- 4、当子类的方法实现父类的方法时(重载/重写,或实现),方法的后置条件(输出/返回值)要比父类更加严格或相等
- 所以在之前的开闭原则示例中,因为修改了重写方法,而违背了里氏替换原则,此时我们应该在子类GoodHopPeople中定义一个新的方法来完成新的任务。
- 使用里氏替换原则有以下优点:
- 1、约束继承泛滥,开闭原则的一种体现
- 2、加强程序的健壮性,同时提高可维护性、扩展性和兼容性,降低了银需求变更带来的风险。
- 还是用一个非常经典的场景来描述一下,那就是正方形、矩形和四边形,我们先创建一个长方形:
public class Rectangle {
private Long height;
private Long width;
public Long getHeight() {
return height;
}
public void setHeight(Long height) {
this.height = height;
}
public Long getWidth() {
return width;
}
public void setWidth(Long width) {
this.width = width;
}
}
- 创建一个正方形继承自长方形:
public class Square extends Rectangle {
private Long length;
public Long getLength() {
return length;
}
public void setLength(Long length) {
this.length = length;
}
@Override
public Long getHeight() {
return getLength();
}
@Override
public void setHeight(Long height) {
setLength(height);
}
@Override
public Long getWidth() {
return getLength();
}
@Override
public void setWidth(Long width) {
setLength(width);
}
}
- 现在写一个测试类,让长方形的高一直自增,直到等于宽,变成正方形
public class ChangeLength {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setWidth(10L);
rectangle.setHeight(5L);
change(rectangle);
}
public static void change(Rectangle rectangle) {
while (rectangle.getWidth() rectangle.getHeight()) {
rectangle.setHeight(rectangle.getHeight() + 1);
System.out.println("width:"+rectangle.getWidth() + ",height:"+rectangle.getHeight());
}
System.out.println("change 方法结束" + "\nwidth:"+rectangle.getWidth() + ",height:"+rectangle.getHeight());
}
}
- 看下运行结果:
- 最后得到了一个10*10的正方形。那么如果现在把长方形改为正方形,又会怎样呢,我们看下:
public static void change(Rectangle rectangle) {
while (rectangle.getWidth() > rectangle.getHeight()) {
rectangle.setHeight(rectangle.getHeight() + 1);
System.out.println("width:"+rectangle.getWidth() + ",height:"+rectangle.getHeight());
}
System.out.println("change 方法结束" + "\nwidth:"+rectangle.getWidth() + ",height:"+rectangle.getHeight());
}
public static void main(String[] args) {
Square square = new Square();
square.setLength(10L);
change(square);
}
- 程序陷入了死循环,违背了里氏替换原则,用子类替换父类后,并没有打到我们语气的结果,里氏替换原则只存在于父类与子类之间,约束继承泛滥。我们现在再定义一个四边形:
public interface Quadrangle {
long getWidth();
long getHeight();
}
- 修改长方形:
public class Rectangle implements Quadrangle {
private Long height;
private Long width;
@Override
public long getHeight() {
return height;
}
public void setHeight(Long height) {
this.height = height;
}
@Override
public long getWidth() {
return width;
}
public void setWidth(Long width) {
this.width = width;
}
}
- 修改正方形:
public class Square extends Rectangle implements Quadrangle {
private Long length;
public Long getLength() {
return length;
}
public void setLength(Long length) {
this.length = length;
}
@Override
public long getHeight() {
return getLength();
}
@Override
public long getWidth() {
return getLength();
}
}
- 此时把change方法的参数改为Quadrangle 类,方法就会报错,因为正方形内部已经没有了setWidth()和 setHeight()方法了。为了约束继承泛滥,change方法的参数只能用Rectangle 长方形。
7.合成复用原则
- 合成复用原则(Composite/Aggregate Reuse Principle,CARP)是指尽量使用对象组合(has-a)/聚合(contains-a),而不是继承关系达到软件复用的目的。可以使系统更加灵活,降低类与类之间的耦合度,一个类对另一个类的影响相对较小。继承可以称为白箱复用,相当于把所有的实现细节暴露给子类,组合/聚合相当于黑箱复用,对类以外的对象无法得知具体功能的实现细节。
- 以数据库连接操作为例,首先定义一个MySQL连接方法类:
public class MySqlConnection {
public String getConnection() {
return "成功连接MySQL";
}
}
- 在DAO层调用,并操作数据库
public class OperateDao {
private MySqlConnection connection;
public void setConnection(MySqlConnection connection) {
this.connection = connection;
}
public void doTask() {
String conn = connection.getConnection();
System.out.println(conn + "并成功操作");
}
}
- 调用的代码只管调用连接方法就可以了,不用去关心到底是怎么连接的。
总结:
- 在实际开发过程中,并不是一定要求所有代码都 遵循设计原则,我们要考虑人力、时间、成本、质量,不是刻意追求完美,要在适当的 场景遵循设计原则,体现的是一种平衡取舍,帮助我们设计出更加优雅的代码结构。
来源:CSDN
作者:junehozhao
链接:https://blog.csdn.net/baidu_37147070/article/details/103802728