00_6面向对象设计原则-里氏替换原则
定义:里氏替换原则指如果对每一个类型为T1和对象O1,都有类型T2的对象O2,使得以T1定义的所有程序P在所有对象O1都替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
根据上述理解,对里氏替换原则的定义可以总结如下:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
- 子类中可以增加自己特有的方法
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松
- 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等
下面我们来描述一个经典的业务场景,用正方形、巨型和四边形的关系说明里氏替换原则,我们都知道正方形是一个特殊的长方形,那么可以创建一个长方形的父类Rectangle类,代码如下:
package org.test.design.principle.lsp; 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; } }
Square.java
package org.test.design.principle.lsp; 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 static void resize(Rectangle rectangle) { while (rectangle.getWidth() >= rectangle.getHeight()) { rectangle.setHeight(rectangle.getHeight() + 1); System.out.println("Width:" + rectangle.getWidth() + ",Height:" + rectangle.getHeight()); } System.out.println("Resize End,Width:" + rectangle.getWidth() + ",Height:" + rectangle.getHeight()); } }
测试:
public static void main(String[] args) { Rectangle rectangle = new Rectangle(); rectangle.setWidth(20); rectangle.setHeight(10); resize(rectangle); }
输出:
Width:20,Height:11
Width:20,Height:12
Width:20,Height:13
Width:20,Height:14
Width:20,Height:15
Width:20,Height:16
Width:20,Height:17
Width:20,Height:18
Width:20,Height:19
Width:20,Height:20
Width:20,Height:21
Resize End,Width:20,Height:21
看到结果可知,高比宽大,这在长方形中是一种非常正常的情况,在来看下面的代码,把长方形替换成它的子类正方形,修改客户端测试代码:
public static void main(String[] args) { Square square = new Square(); square.setLength(10); resize(square); }
此时,运行出现死循环,违背了里氏替换原则,在将父类替换为子类后,程序运行结果没有达到预期。因此,代码设计存在一定的风险的。里氏替换原则只存在于父类与子类之间,约束继承泛滥。再来创建一个基于长方形的共同的抽象--四边形接口,代码如下:
package org.test.design.principle.lsp; public interface QuardRangle { long getWidth(); long getHeight(); }
在来看一下长方形类的V2版本:
package org.test.design.principle.lsp; public class RectangleV2 implements QuardRangle{ private long height; private long width; public void setHeight(long height) { this.height = height; } public void setWidth(long width) { this.width = width; } @Override public long getWidth() { return width; } @Override public long getHeight() { return height; } }
在看看正方形类的V2版本:
package org.test.design.principle.lsp; /** * TODO * * @author Administrator * @version 1.0 * @date 2021/11/17 20:55 */ public class SquareV2 implements QuardRangle { private long length; public long getLength() { return length; } public void setLength(long length) { this.length = length; } @Override public long getWidth() { return length; } @Override public long getHeight() { return length; } }
测试:
public static void resize(RectangleV2 square) { while (square.getWidth() >= square.getHeight()) { square.setHeight(square.getHeight() + 1); System.out.println("Width:" + square.getWidth() + ",Height:" + square.getHeight()); } System.out.println("Resize End,Width:" + square.getWidth() + ",Height:" + square.getHeight()); } public static void main(String[] args) { RectangleV2 rectangle = new RectangleV2(); rectangle.setWidth(20); rectangle.setHeight(10); resize(rectangle); }
我们在来看一下,如果把resize()方法的参数换成形QuardRangleV2类,方法内部就会报错。因为正方形已经没有了setWidth()和setHeigth()方法,所以,为了约束继承泛滥,resize()方法的参数只能用长方形RectangleV2类。
此篇完。