6大设计原则(二)—里氏替换原则
最后更新于:2022-04-01 16:25:42
## 里氏替换原则
英文名称(Liskov Substitution Principle,LSP):
定义:**所有引用基类的地方必须能够透明地使用其子类的对象**
我的理解: **父类出现的地方也可以用子类来替换,而子类出现的地方父类不一定能替换。**
里氏替换原则的为继承定义的规范,包括4层含义
**1、子类必须完全实现父类的方法**
**2、子类可以有自己的个性**
**3、覆盖或实现父类的方法时输入参数可以被放大**
**4、覆写或实现父类的方法时输出结果可以被缩小**
模拟CS游戏
战士可以拿枪去杀敌人,setGun()给战士设置不同的枪。killEnemy()射杀敌人。
抽象类,AbstractGun,void shoot()方法
枪类MachineGun(机枪),Rifle(步枪),Handgun(手枪)实现抽象类方法
-----这个类的设计原理是,每种枪都有一个“射击”的方法,因此抽象出来使用。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-06_57553406749c5.jpg)
~~~
public class Test {
public static void main(String[] args) {
Soldier s = new Soldier();
s.setGun(new Handgun());
s.killEnemy();
}
}
abstract class AbstractGun{
public void shoot(){};
}
/**
* 战士类具有杀死敌人的方法,设置不同类别的枪的方法
* @author admin
*
*/
class Soldier{
private AbstractGun gun;
public void setGun(AbstractGun gun){
this.gun = gun;
}
public void killEnemy(){
gun.shoot();
System.out.println("正在射杀敌人...");
}
}
class MachineGun extends AbstractGun{
@Override
public void shoot() {
System.out.println("机关枪扫射...");
}
}
class Rifle extends AbstractGun{
@Override
public void shoot() {
System.out.println("步枪射击...");
}
}
class Handgun extends AbstractGun{
@Override
public void shoot() {
System.out.println("手枪射击...");
}
}
~~~
## 子类必须完全实现父类的方法
当如果又来了一个玩具枪时,我们一贯的思想是,将玩具枪继承AbstractGun类,实现shoot方法。
但是依照情景玩具枪不具备射击的能力。因此玩具枪不能直接继承AbstractGun类。因为它不能完整的实现父类的方法。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-06_575534068bd2f.jpg)
## 子类可以有自己的个性
这句话很好理解,子类继承父类,不仅拥有父类的方法和属性,而且自己还可以用于其他的方法和属性,并且可以覆写父类的方法,重载父类的方法等。![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-06_57553406a89e2.jpg)
~~~
<span style="font-size:18px;">public class Test {
public static void main(String[] args) {
/* Soldier s = new Soldier();
s.setGun(new Handgun());
s.killEnemy();
System.out.println("----------------");*/
Snipper juji = new Snipper();
juji.setGun(new AUG());
juji.killEnemy();
}
}
abstract class AbstractGun{
public void shoot(){};
}
class Snipper{
private AUG aug;
public void setGun(AUG gun){
this.aug = gun;
}
public void killEnemy(){
aug.zoomOut();
aug.shoot();
System.out.println("狙击手正在射杀敌人...");
}
}
class AUG extends Rifle{
@Override
public void shoot() {
System.out.println("狙击枪射击...");
}
public void zoomOut(){
System.out.println("狙击枪正在瞄准");
}
}</span>
~~~
**覆盖或实现父类的方法时输入参数可以被放大**
---这句话类似于子类重载父类的方法时,保证子类的传入参数的范围大于父类的参数。
如果没有满足以上条件的话,就不满足父类存在的地方子类也存在的条件,因而违背了里氏替换原则。
例如两个代码的比较:
~~~
package hpu.lzl.lsp;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class Test02 {
public static void main(String[] args) {
Father f = new Father();
HashMap<String, String> hashMap = new HashMap<String, String>();
f.doSomething(hashMap);
System.out.println("里氏替换----------------父类存在的地方子类应该也可以存在-----------");
Son s = new Son();
s.doSomething(hashMap);
}
}
/**
* 定义一个父类,实现将map集合转换成Collection集合
* @author admin
*
*/
class Father{
public Collection doSomething(HashMap<String,String> map){
System.out.println("父类方法被执行...");
return map.values();
}
}
/**
* 子类重载父类的方法
* @author admin
*
*/
class Son extends Father{
/**
* 注意此处是重载,返回值类型,方法名相同,传入参数不同。
* 保证传入的参数类型的范围大于父类。
*/
public Collection doSomething(Map<String, String> map) {
System.out.println("子类方法被执行...");
return map.values();
}
}
~~~
输出结果:
~~~
父类方法被执行...
里氏替换----------------父类存在的地方子类应该也可以存在-----------
父类方法被执行...
~~~
方法二:
~~~
package hpu.lzl.lsp;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class Test02 {
public static void main(String[] args) {
Father f = new Father();
HashMap<String, String> hashMap = new HashMap<String, String>();
f.doSomething(hashMap);
System.out.println("里氏替换----------------父类存在的地方子类应该也可以存在-----------");
Son s = new Son();
s.doSomething(hashMap);
}
}
/**
* 定义一个父类,实现将map集合转换成Collection集合
* @author admin
*
*/
class Father{
public Collection doSomething(Map<String,String> map){
System.out.println(".......父类方法被执行...");
return map.values();
}
}
/**
* 子类重载父类的方法
* @author admin
*
*/
class Son extends Father{
/**
* 注意此处是重载,返回值类型,方法名相同,传入参数不同。
*/
public Collection doSomething(HashMap<String, String> map) {
System.out.println("子类方法被执行...");
return map.values();
}
}
~~~
输出结果:
~~~
.......父类方法被执行...
里氏替换----------------父类存在的地方子类应该也可以存在-----------
子类方法被执行...
~~~
## 覆写或实现父类的方法时输出结果可以被缩小
分两类解释:一,子类覆写父类的方法时,要求子类与父类的方法名相同,输入的参数相同,返回值值范围小于或等于父类的方法。二,子类重载父类的方法时,要求方法的输入参数类型或数量不同,在里氏替换原则要求下,子类的输入参数要大于或等于子类的输入参数。
我的理解:里氏替换原则,就是在继承的概念上有定义了一些开发时需要的规范。例如子类继承父类时,需要拥有父类的方法(这个意思是,在给对象分类时,一定要抽取相同的属性的方法。例如玩具枪虽然属性与其他枪相同,但是不能杀死敌人,因而不能继承AbstractGun类。)。严格按照这个规范来进行开发,为后期版本升级,增添子类时都可以很好的维护。