[关闭]
@Tyhj 2018-07-15T05:33:07.000000Z 字数 2600 阅读 913

Android设计模式三:里氏替换原则

设计模式


原文链接:https://www.zybuluo.com/Tyhj/note/1198744

核心思想

在使用基类的的地方可以任意使用其子类,能保证子类完美替换基类
通俗来讲,只要父类能出现的地方子类就能出现。反之,父类则未必能胜任

好处

增强程序的健壮性,即使增加了子类,原有的子类还可以继续运行

对于基类中定义的所有子程序,用在它的任何一个派生类中时的含义都应该是相同的。
这样继承才不会增加复杂度,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。
如果我们必须要不断地思考不同派生类的实现在语义上的差异,继承就只会增加复杂度了

怎样来遵循

1、子类必须完全实现父类的方法,不能覆盖父类的非抽象方法

举个栗子

“正方形不是长方形”是一个理解里氏代换原则的最经典的例子。在数学领域里,正方形毫无疑问是长方形,它是一个长宽相等的长方形。我们开发的一个与几何图形相关的软件系统中,让正方形继承自长方形是顺利成章的事情。

定义一个长方形类:

  1. public class Rectangle {
  2. private int width;
  3. private int height;
  4. public int getWidth() {
  5. return width;
  6. }
  7. public void setWidth(int width) {
  8. this.width = width;
  9. }
  10. public int getHeight() {
  11. return height;
  12. }
  13. public void setHeight(int height) {
  14. this.height = height;
  15. }
  16. }

定义一个正方形类,继承长方形:

  1. public class Square extends Rectangle {
  2. @Override
  3. public void setWidth(int width) {
  4. super.setWidth(width);
  5. super.setHeight(width);
  6. }
  7. @Override
  8. public void setHeight(int height) {
  9. super.setHeight(height);
  10. super.setWidth(height);
  11. }
  12. }

类LSPTest是我们的软件系统中的一个组件,它有一个resize方法要用到基类Rectangle,resize方法的功能是模拟长方形宽度逐步增长的效果:

  1. public class LSPTest {
  2. /**
  3. * 让长方形的宽增加到比长大
  4. *
  5. * @param objRect
  6. */
  7. public void resize(Rectangle objRect) {
  8. while (objRect.getWidth() <= objRect.getHeight()) {
  9. objRect.setWidth(objRect.getWidth() + 1);
  10. }
  11. }
  12. }

我们运行一下这段代码就会发现,假如我们把一个普通长方形作为参数传入resize方法,就会看到长方形宽度逐渐增长的效果,当宽度大于长度,代码就会停止,这种行为的结果符合我们的预期;假如我们再把一个正方形作为参数传入resize方法后,就会看到正方形的宽度和长度都在不断增长,代码会一直运行下去,直至系统产生溢出错误。所以,普通的长方形是适合这段代码的,正方形不适合。
我们得出结论:在resize方法中,Rectangle类型的参数是不能被Square类型的参数所代替,如果进行了替换就得不到预期结果。因此,Square类和Rectangle类之间的继承关系违反了里氏代换原则,它们之间的继承关系不成立,正方形不是长方形。

简单的解决办法

  1. public void resize(Rectangle objRect) {
  2. if(objRect instanceof Square){
  3. return;
  4. }
  5. if(objRect instanceof Rectangle){
  6. while (objRect.getWidth() <= objRect.getHeight()) {
  7. objRect.setWidth(objRect.getWidth() + 1);
  8. }
  9. }
  10. }

效果好像不错,干嘛讲究那么多呢,实现需求是第一位的,这种写法看起来很很直观的,有利于维护。其实这是违背里氏代换原则的,结果就是可维护性和可扩展性会变差。

建议的解决办法

可以增加一个抽象类Quadrangle,定义四边形的公共方法,Square和Rectangle都从Quadrangle继承这些方法,同时可以添加自己特有的方法(对于resize方法,正方形不继承长方形,就不存在问题了)。

2、子类中可以增加自己特有的方法

3、覆盖或者实现父类的方法时输入参数可以被放大

  1. import java.util.HashMap;
  2. public class Father {
  3. public void func(HashMap m){
  4. System.out.println("执行父类...");
  5. }
  6. }
  7. import java.util.Map;
  8. public class Son extends Father{
  9. public void func(Map m){//方法的形参比父类的更宽松
  10. System.out.println("执行子类...");
  11. }
  12. }
  13. import java.util.HashMap;
  14. public class Client{
  15. public static void main(String[] args) {
  16. Father f = new Son();//引用基类的地方能透明地使用其子类的对象。
  17. HashMap h = new HashMap();
  18. f.func(h);
  19. }
  20. }

4、覆盖或者实现父类的方法时输出结果可以被缩小

  1. import java.util.Map;
  2. public abstract class Father {
  3. public abstract Map func();
  4. }
  5. import java.util.HashMap;
  6. public class Son extends Father{
  7. @Override
  8. public HashMap func(){//方法的返回值比父类的更严格
  9. HashMap h = new HashMap();
  10. h.put("h", "执行子类...");
  11. return h;
  12. }
  13. }
  14. public class Client{
  15. public static void main(String[] args) {
  16. Father f = new Son();//引用基类的地方能透明地使用其子类的对象。
  17. System.out.println(f.func());
  18. }
  19. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注