【设计模式】软件设计七大原则 ( 里氏替换原则 | 代码示例 | 类示例 | 方法入参示例 | 方法返回值示例 )
一、里氏替换原则代码示例 ( 类的层级 | 反面示例 )
在下面的类中 , 定义了长方形类 , 然后定义了长方形类的子类 , 正方形类 ;
其中在 正方形类中 , 重写了 长方形类的方法 ;
该操作明显违反了里氏替换原则 ;
根据里氏替换原则 , 子类只能实现抽象方法 , 不允许覆盖已有的非抽象方法 ;
1、长方形
package liskovsubstitution;
/**
* 长方形
*/
public class Rectangle {
/**
* 长方形长
*/
private long length;
/**
* 长方形宽
*/
private long width;
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public long getWidth() {
return width;
}
public void setWidth(long width) {
this.width = width;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
2、正方形
package liskovsubstitution;
/**
* 正方形
*/
public class Square extends Rectangle {
/**
* 正方形边长
*/
private long sideLength;
public long getSideLength() {
return sideLength;
}
public void setSideLength(long sideLength) {
this.sideLength = sideLength;
}
/**
* 重写正方形获取长度的方法
* 正方形的长和宽都是边长
* @return
*/
@Override
public long getLength() {
return getSideLength();
}
/**
* 重写正方形设置长度的方法
* 正方形的长和宽都是边长
* @param length
*/
@Override
public void setLength(long length) {
setSideLength(length);
}
/**
* 重写正方形获取长度的方法
* 正方形的长和宽都是边长
* @return
*/
@Override
public long getWidth() {
return getSideLength();
}
/**
* 重写正方形设置宽度的方法
* 正方形的长和宽都是边长
* @param width
*/
@Override
public void setWidth(long width) {
setSideLength(width);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
二、里氏替换原则代码示例 ( 类的层级 | 正面示例 )
子类的行为 要和 父类 保持一致 , 如果无法达到这一点 , 就无法遵守里氏替换原则 ;
使用继承时 , 一定要慎重 ;
1、四边形接口
package liskovsubstitution;
/**
* 四边形
* 之前将 正方形 设置为 长方形 的子类不合理
* 这里抽象出一个四边形接口
* 令 长方形 和 正方形 都实现该 四边形接口
*/
public interface Quadrangle {
/**
* 获取长度
* @return
*/
long getLength();
/**
* 获取宽度
* @return
*/
long getWidth();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
2、长方形类
package liskovsubstitution;
/**
* 长方形
*/
public class Rectangle implements Quadrangle {
/**
* 长方形长
*/
private long length;
/**
* 长方形宽
*/
private long width;
@Override
public long getLength() {
return this.length;
}
@Override
public long getWidth() {
return this.width;
}
public void setLength(long length) {
this.length = length;
}
public void setWidth(long width) {
this.width = width;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
3、正方形类
package liskovsubstitution;
/**
* 正方形
*/
public class Square implements Quadrangle {
/**
* 正方形边长
*/
private long sideLength;
@Override
public long getLength() {
return this.sideLength;
}
@Override
public long getWidth() {
return this.sideLength;
}
public long getSideLength() {
return sideLength;
}
public void setSideLength(long sideLength) {
this.sideLength = sideLength;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
三、里氏替换原则代码示例 ( 方法入参 | 正面示例 )
重载 ( 输入参数 宽松 ) : 子类的方法 重载 父类的方法 时 , 方法的前置条件 ( 输入参数 ) , 要比 父类方法的输入参数更宽松 ;
如 : 父类的参数是 HashMap , 如果要符合 里氏替换原则 , 子类如果重载父类方法 , 那么需要使用 Map 类型参数 ;
( 这里注意区分 重写 与 重载 , 重写是重写父类方法 , 重载是函数名相同 , 参数不同 )
如果在父类中参数类型是 Map , 在子类中重载参数类型是 HashMap , 这样就会出现混乱的问题 ;
客户端调用时 , 可能不清楚情况 , 加入传入了 HashMap 参数 , 此时就有可能出现混乱 , 无法调用到 父类/子类的 正常重写方法 , 方法调用被重载方法拦截的情况 ;
如果 重载的方法 的参数 比父类的方法参数更严格 , 那么这就不是重载方法 , 而是重写方法 ;
1、父类
package liskovsubstitution;
import java.util.HashMap;
public class Father {
public void method(HashMap map) {
System.out.println("执行父类 void method(HashMap map) 方法");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2、子类
package liskovsubstitution;
import java.util.HashMap;
import java.util.Map;
public class Child extends Father {
/**
* 子类重写父类的方法
* 重写 ( 返回值 严格 ) : 当 子类的方法 重写 / 重载 / 实现 父类的方法时
* 方法的 后置条件 ( 返回值 ) 要 比父类更严格或相等 ;
* @param map
*/
@Override
public void method(HashMap map) {
System.out.println("执行子类重写的 void method(HashMap map) 方法");
}
/**
* 重载的方法
* 重载 ( 输入参数 宽松 ) : 子类的方法 重载 父类的方法 时
* 方法的前置条件 ( 输入参数 ) , 要比 父类方法的输入参数更宽松 ;
*
* 如果在父类中参数类型是 Map
* 在子类中重载参数类型是 HashMap
* 这样就会出现混乱的问题
* 客户端调用时 , 可能不清楚情况 , 加入传入了 HashMap 参数
* 此时就有可能出现混乱 , 无法调用到 父类/子类的 正常重写方法
* 方法调用被重载方法拦截的情况
*
* 如果 重载的方法 的参数 比父类的方法参数更严格
* 那么这就不是重载方法 , 而是重写方法
*
* 遵守里氏替换原则很有必要
* @param map
*/
public void method(Map map) {
System.out.println("执行子类重载的 void method(Map map) 方法");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
3、测试类
package liskovsubstitution;
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
Child child = new Child();
HashMap hashMap = new HashMap();
// 此时传入的 HashMap 参数
// 由于重载的方法接收的参数是 Map 类型的
// 此时调用的是父类的方法 或 子类重写的 void method(HashMap map) 方法
// 不会调用重载的 void method(Map map) 方法
child.method(hashMap);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
执行结果 :
执行子类重写的 void method(HashMap map) 方法
- 1
四、里氏替换原则代码示例 ( 方法入参 | 反面示例 )
在该反面示例中 , 父类中的方法参数是 Map 类型 , 子类中重载的方法参数是 HashMap 类型 ;
如果客户端调用该方法 , 传入一个 HashMap 类型的参数 , 就会出现只能调用重载方法 , 无法调用父类中定义的方法或子类中重写的方法 ;
重载的方法比父类方法参数严格 , 就会出现上述情况 ;
1、父类
package liskovsubstitution;
import java.util.Map;
public class Father {
public void method(Map map) {
System.out.println("执行父类 void method(HashMap map) 方法");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2、子类
package liskovsubstitution;
import java.util.HashMap;
import java.util.Map;
public class Child extends Father {
/**
* 子类重写父类的方法
* 重写 ( 返回值 严格 ) : 当 子类的方法 重写 / 重载 / 实现 父类的方法时
* 方法的 后置条件 ( 返回值 ) 要 比父类更严格或相等 ;
* @param map
*/
@Override
public void method(Map map) {
System.out.println("执行子类重写的 void method(HashMap map) 方法");
}
/**
* 重载的方法
* 重载 ( 输入参数 宽松 ) : 子类的方法 重载 父类的方法 时
* 方法的前置条件 ( 输入参数 ) , 要比 父类方法的输入参数更宽松 ;
*
* 如果在父类中参数类型是 Map
* 在子类中重载参数类型是 HashMap
* 这样就会出现混乱的问题
* 客户端调用时 , 可能不清楚情况 , 加入传入了 HashMap 参数
* 此时就有可能出现混乱 , 无法调用到 父类/子类的 正常重写方法
* 方法调用被重载方法拦截的情况
*
* 如果 重载的方法 的参数 比父类的方法参数更严格
* 那么这就不是重载方法 , 而是重写方法
*
* 遵守里氏替换原则很有必要
* @param map
*/
public void method(HashMap map) {
System.out.println("执行子类重载的 void method(Map map) 方法");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
3、测试类
package liskovsubstitution;
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
Child child = new Child();
HashMap hashMap = new HashMap();
// 此时传入的 HashMap 参数
// 由于重载的方法接收的参数是 Map 类型的
// 此时调用的是父类的方法 或 子类重写的 void method(HashMap map) 方法
// 不会调用重载的 void method(Map map) 方法
child.method(hashMap);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
执行结果 :
执行子类重载的 void method(Map map) 方法
- 1
五、里氏替换原则代码示例 ( 方法返回值 )
重写 ( 返回值 严格 ) : 当 子类的方法 重写 / 重载 / 实现 父类的方法时 , 方法的 后置条件 ( 返回值 ) 要 比父类更严格或相等 ;
如 : 父类的返回值是 Map , 子类的相同的方法 是 Map 或 HashMap ;
该错误基本不可能触犯 , 因为编译时会检查 , 如果发现子类的实现方法的返回值 大于 父类方法 , 编译时会报错 , 下图就是编译报错情况 :
1、父类
package liskovsubstitution;
import java.util.Map;
public abstract class Father {
public abstract Map method();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
2、子类
package liskovsubstitution;
import java.util.HashMap;
public class Child extends Father {
/**
* 子类重写父类的方法
* 重写方法的返回值 , 严格程度 , 一定要小于等于父类方法的返回值
* @return
*/
@Override
public HashMap method() {
System.out.println("子类实现的父类的 HashMap method() 被执行");
HashMap hashMap = new HashMap();
return hashMap;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
3、测试类
package liskovsubstitution;
public class Main {
public static void main(String[] args) {
Child child = new Child();
child.method();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
执行结果 :
子类实现的父类的 HashMap method() 被执行
- 1
文章来源: hanshuliang.blog.csdn.net,作者:韩曙亮,版权归原作者所有,如需转载,请联系作者。
原文链接:hanshuliang.blog.csdn.net/article/details/119897392
- 点赞
- 收藏
- 关注作者
评论(0)