LiskovSubstitutionPrinciple(LSP)是指如果对于每一个T1类型的对象o1,都存在T2类型的对象O2,使得T1定义的对象对于所有程序P,当所有对象O1被替换为O2,程序P的行为没有改变,则类型T2是类型T1的子类型。这个定义好像比较抽象,我们重新理解一下。可以这样理解,如果一个软件实体适用于父类,那么它也一定适用于它的子类。所有对父类的引用必须能够透明地使用其子类的对象。子类对象可以替代父类对象,程序逻辑常量。按照这种理解,扩展的意思就是:子类可以扩展父类的功能,但不能改变父类原有的功能。(1)子类可以实现父类的抽象方法,但不能重写父类的非抽象方法。(2)子类可以添加自己特有的方法。(3)当子类的方法重写父类的方法时,方法的前置条件(即方法的输入/参数)比父类方法的输入参数要宽松。(4)子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)严格或不同于与父类相同。在讲开闭原理的时候,我就埋下了伏笔。在获取折扣的时候,我重写了覆盖父类的getPrice()方法,增加了一个获取源码的方法getOriginPrice(),显然违反了里氏代换原则。我们修改一下代码,getPrice()方法不要被重写,应该增加getDiscountPrice()方法:价格);}publicDoublegetDiscountPrice(){返回super.getPrice()*0.61;}}使用里氏代换原则有以下优点:(1)约束继承溢出是开闭原则的体现。(2)加强程序的健壮性,同时在变更时实现很好的兼容性,提高程序的可维护性和可扩展性,降低需求变更时引入的风险。下面我们来描述一个经典的业务场景,用正方形、长方形和四边形的关系来说明里氏代换原则。我们都知道正方形是一种特殊的矩形,所以我们可以创建一个父类Rectangle:publicclassRectangle{privatelongheight;私人长宽;@OverridepubliclonggetWidth(){返回宽度;}@OverridepubliclonggetLength(){返回长度;}publicvoidsetLength(longlength){this.length=length;}publicvoidsetWidth(longwidth){this.width=width;}}创建正方形状类Square继承Rectangle类:publicclassSquareextendsRectangle{privatelonglength;publiclonggetLength(){返回长度;}publicvoidsetLength(longlength){this.length=length;}@OverridepubliclonggetWidth(){返回getLength();}@OverridepubliclonggetHeight(){返回getLength();}@OverridepublicvoidsetHeight(longheight){setLength(height);}@OverridepublicvoidsetWidth(longwidth){setLength(width);}}在测试类中创建一个resize()方法。矩形的宽度应大于或等于高度。我们让高度增加,直到高度等于宽度,变成一个正方形:()+1);System.out.println("width:"+rectangle.getWidth()+",height:"+rectangle.getHeight());}System.out.println("resize方法结束"+"\nwidth:"+rectangle.getWidth()+",height:"+rectangle.getHeight());}测试代码如下:publicstaticvoidmain(String[]args){矩形rectangle=newRectangle();rectangle.setWidth(20);rectangle.setHeight(10);resize(rectangle);}运行结果如下图,我们发现高度大于宽度。矩形中的一个非常正常的情况。现在我们用它的子类Square替换Rectangle类并修改测试代码:publicstaticvoidmain(String[]args){Squaresquare=newSquare();square.setLength(10);resize(square);}上面的代码运行时存在死循环,违反了里氏代换原则。将父类替换为子类后,程序运行结果不符合预期。因此,我们的代码设计存在一定的风险。里氏代换原则只存在于父类和子类之间,约束继承泛滥。让我们创建一个基于常见矩形和正方形的抽象四边形接口Quadrangle:publicinterfaceQuadrangle{longgetWidth();longgetHeight();}修改矩形classRectangle:publicclassRectangleimplementsQuadrangle{privatelongheight;私人长@宽度;覆盖publiclonggetWidth(){返回宽度;}publiclonggetHeight(){返回高度;}publicvoidsetHeight(longheight){this.height=height;}publicvoidsetWidth(longwidth){this.width=width;}}修改正方形状Square:publicclassSquareimplementsQuadrangle{privatelonglength;publiclonggetLength(){返回长度;}publicvoidsetLength(longlength){this.length=length;}@OverridepubliclonggetWidth(){返回长度;}}@OverridepubliclonggetHeight(){返回长度;}}此时,如果我们将resize()方法的参数换成四边形接口Quadrangle,方法内部就会报错。因为方形类Square没有setWidth()和setHeight()方法。因此,为了限制继承的泛滥,resize()方法的参数只能使用Rectangle类。当然,后面我们会在设计模式的内容中继续深入讲解。本文为《汤姆炸弹建筑》原创,转载请注明出处。科技在于分享,我分享我的快乐!如果本文对您有帮助,请关注并点赞;有什么建议也可以留言或私信。您的支持是我坚持创作的动力。关注微信公众号“汤姆炸弹架构”,获取更多技术干货!【推荐】汤姆炸弹架构:收藏此文相当于收藏一本《设计模式》书其他设计原则汤姆炸弹架构:开闭原则(OCP)汤姆炸弹架构:单一职责(SimpleResponsibilityPrinciple,SRP)汤姆炸弹架构:接口隔离原则(InterfaceSegregationPrinciple,ISP)汤姆炸弹架构:迪米特法则LoD(LawofDemeterLoD)汤姆炸弹架构:合成复杂Composite/AggregateReuse原理(鲤鱼)
