#软件构造 LSP里氏原则与软件设计

这一篇主要对 4.2节面向复用性的软件设计进行总结

里氏替代原则1:行为子类型

首先,LSP全称为里氏(Liskov )替代原则——Liskov Substitution Principle ,是面向对象最重要的几大原则之一。

从字面上去指,其含义是:

  • 子类型可以增加方法,但不可删
  • 子类型需要实现抽象类型中的所有未实现方法
  • 子类型中重写(Override)的方法 必须有相同或子类型的返回值
  • 子类型中重写(Override)的 方法必须使用同样类型或者父类型的参数
  • 子类型中重写(Override)的方法不能抛出额外的异常

很显然上面的原则已经写进了Java的语法规则里,如果不满足无法通关静态类型检查

除此之外,还需要遵守一些原则,虽然编译器不会强制我们遵守,但是如果想要写出复用性高的代码要做到:

  • 子类型拥有相同或更强的不变量
  • 子类型拥有相同或更弱的前置条件
  • 子类型拥有相同或更强的后置条件

简答说就是子类型需要有至少不弱于父类型的不变量,同时规约也需要更强
比如下面的例子:
在这里插入图片描述
在上面的例子中,混合动力汽车中,

  • 相较于父类中的不变量fuel保证了相同的强弱,又新增加了不变量charge
  • 重写后的start()方法的前置条件要比父类中的更弱
  • 重写后的而brake()的后置条件要比父类中的更强

里氏替代原则2:逆变与协变

首先给出逆变与协变的定义,看上去都很好理解:

  • 协变:父类型→子类型:越来越具体
  • 逆变:子 类型→父类型:越来越抽象

显然,在上面的LSP原则中,子类型方法参数发生了逆变 ,子类型方法的返回值发生了协变 ,而子类型抛出的异常类型发生了协变

举个例子:

  • 数组是协变的: 一个定义为T[]的数组可以包含类型为T或者是T的子类的元素

里氏替代原则3:泛型

1.泛型与类型擦除

众所周知,instanceof不能来检验泛型的类型的信息,因为在编译后的运行时泛型消失了
左右分别为编译前后
编译时,如果泛型类型未指定,用Object来替换泛型,否则用指定类型来替换,因此在生成的字节码中只包含普通的类、接口和方法。

例如:

 List<Integer> myInts = new ArrayList<Integer>();
 myInts.add(1); 
 myInts.add(2); 
 List<Number> myNums = myInts; //compiler error
 myNums.add(3.14)

因为我们不能认为List< Integer >是 List< Number >的子类型

public static void main(String args[]) {
		List<Integer> myInts =Arrays.asList(1, 2, 3, 4, 5);
		List<Long> myLongs = Arrays.asList(1L, 2L, 3L, 4L, 5L);
		List<Double> myDoubles = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0);
		sum(myInts);//编译错误  sum(List<Number>)对于参数(List<Long>)不适用
		sum(myLongs);//编译错误  sum(List<Number>)对于参数(List<Long>)不适用
		sum(myDoubles); //编译错误  sum(List<Number>)对于参数(List<Long>)不适用
	}

	static long sum(List<Number> numbers) {
		long summation = 0;
		for (Number number : numbers) {
			summation += number.longValue();
		}
		return summation;
	}

比如这一段代码中 编译器提示我们 sum(List< Number>) 对于参数 (List< Long >) 不适用

那么,对于MyClass< A> 和 MyClass< B > 类, 无论A和B有什么样的关系,MyClass< A > 和 MyClass< B > 类都没有类型之间的关系,他们的共同父类只是Object类。

因此,因为类型擦除的缘故,泛型是类型不变的,而非协变或者逆变的。

2. 一些特殊的泛型类型

通配符 < ?> ( wildcard character ), 例如 List< ?>.

而 List< Number>是 List<?>的子类型
在这里插入图片描述
使用情景:

  • 编写一个可以使用 Object 类中提供的功能来实现的方法。
  • 当代码使用泛型类中不依赖于类型参数的方法时。例如, List.size() 或者List.clear()
  • 事实上,Class< ?>在编程中经常被使用,因为Class中的大多数方法不都不需要依赖于泛型标识符< T>

上界通配符<? super A>
可以匹配 A的父类型的泛型
例如:
List< Object>是 List<? super String> 的子类型

下界通配符<? extends A>
可以匹配 A的子类型的泛型
例如:
List< Number> 是 List<? extends Object>的子类型
在这里插入图片描述
对于这三个通配符,与之前不同的是,可以拥有父类与子类的关系

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值