概述
书接上回,本篇继续讲一下设计模式六大原则(有些书认为是7大原则)
原则定义
里氏替换原则作者芭芭拉·利斯科夫(Barbara Liskov)原话: "派生类(子类)对象可以在程式中代替其基类(超类)对象。"
这话怎么理解,那就得复习一下java中的继承核心概念
//父类
public class Father {
public void fatherFun(){
//......
}
}
//子类
public class Son extends Father {
//重写父类方法
@Override
public void fatherFun() {
//.....
}
public void sonFun(){
//.....
}
}
//测试
public class App {
public static void test(Father father){
//.....
}
public static void main(String[] args) {
Father father = new Father();
father.fatherFun(); //执行父类方法
Son son = new Son();
son.sonFun(); //执行子类方法
son.fatherFun(); //执行子类重写后方法
//继承多态
Father father1 = new Son();
father1.fatherFun(); //执行子类重写后方法
father1.sonFun(); //无法执行,编译不通过
//继承多态
App.test(fath
er);
App.test(son);
}
}
归纳一下:
1>子类使用extends继承父类
2>子类可以重写父类定义的方法,同时可自定义方法
3>子类重写父类定义的方法后, 子类再调用父类方法时,执行子类重写后的方法
4>继承多态时,子类替换父类,只能调用父类定义的方法,会遮蔽掉子类自定义方法
5>继承多态时,子类替换父类,调用父类定义的方法时,执行子类重写后的方法
回到里氏替换原则:派生类(子类)对象可以在程式中代替其基类(超类)对象。
作为一个原则,那它具体约束什么,在程序设计过程需要遵守什么呢?
1>父类为抽象类,负责代码重用(普通方法)和规则定制(抽象方法)
2>子类重写父类抽象的方法,不建议重写普通方法
3>能用父类实例的地方,肯定也可以使用子类实例。
案例分析
需求:煮一碗国士无双的面
//面条父类
public abstract class Noodle {
//加水
public abstract void addWater();
//加面条
public abstract void addNoodle();
//调味品
public abstract void addCondiment();
public void cook(){
this.addWater();
System.out.println("水滚汤开.....");
this.addNoodle();
System.out.println("面差不多了.....");
this.addCondiment();
System.out.println("调好味,起锅.....");
}
}
/**
* 担担面
*/
public class DanDanNoodle extends Noodle{
@Override
public void addWater() {
System.out.println("老母鸡鸡汤一锅....");
}
@Override
public void addNoodle() {
System.out.println("面条");
}
@Override
public void addCondiment() {
System.out.println("调红油汁,简单,醋,酱油,红油,葱蒜末");
}
}
/**
* 拉面
*/
public class RamenNoodle extends Noodle{
@Override
public void addWater() {
System.out.println("喜马拉雅山冰雪融水...");
}
@Override
public void addNoodle() {
System.out.println("一大坨热乎的兰州拉面.....");
}
@Override
public void addCondiment() {
System.out.println("兰州高汤....");
}
}
/**
* 锅
*/
public class Pot {
public void cook(Noodle noodle){
noodle.cook();
}
}
测试:
public class App {
public static void main(String[] args) {
Pot pot = new Pot();
Noodle dd = new DanDanNoodle();
Noodle rn = new RamenNoodle();
pot.cook(dd);
System.out.println("--------------");
pot.cook(rn);
}
}
结果:
老母鸡鸡汤一锅....
水滚汤开.....
面条
面差不多了.....
调红油汁,简单,醋,酱油,红油,葱蒜末
调好味,起锅.....
--------------
喜马拉雅山冰雪融水...
水滚汤开.....
一大坨热乎的兰州拉面.....
面差不多了.....
兰州高汤....
调好味,起锅.....
解析
上例中, Noodle类是抽象类,有个普通cook方法,里面按一定顺序(煮面的顺序)调用3个方法 addWater addNoodle addCondiment ,这3个方法是抽象方法, 具体由DanDanNoodle RamenNoodle 根据实际情况去实现。这种是就典型的模板方法设计, 抽象父类制定操作规则, 子类做差异实现。
Pot 锅类要煮面条时, cook方法可以接受Noodle类实例, 也可以接受Noodle的子类对象实例, 这很符合里氏替换原则要求能用父类实例的地方,肯定也可以使用子类实例。
值得注意问题
//方便面
public class InstantNoodle extends Noodle {
@Override
public void addWater() {
System.out.println("倒水");
}
@Override
public void addNoodle() {
System.out.println("加面饼");
}
@Override
public void addCondiment() {
System.out.println("加调味料");
}
@Override
public void cook() {
System.out.println("煮个大头鬼..泡才是灵魂...");
}
}
测试:
public class App {
public static void main(String[] args) {
Pot pot = new Pot();
InstantNoodle noodle = new InstantNoodle();
pot.cook(noodle);
}
}
结果:
煮个大头鬼..泡才是灵魂...
解析
上面方便面类:InstantNoodle, 继承了Noodle, 并重写了cook方法。在代码语法上没有任何问题,但违背了Noodle类cook方法设计初衷。cook方法设计初衷在于煮面条必须经历: 加水, 加面, 加配料3个顺序步骤, InstantNoodle 子类重写了,那就打破最初约定规则。回归到架构上, 如果架构约定任意打破,任意违背,那项目就没有稳定性可言啦。基于此氏替换原则要求子类可以扩展父类的功能,但不能改变父类原有的功能(子类只能重写父类要求子类重写的方法),而不是所有。
改进
在父类的cook方法上加上final, 禁止子类重写
//面条父类
public abstract class Noodle {
//加水
public abstract void addWater();
//加面条
public abstract void addNoodle();
//调味品
public abstract void addCondiment();
//在cook方法上加上final即可, 不允许子类重写
public final void cook(){
this.addWater();
System.out.println("水滚汤开.....");
this.addNoodle();
System.out.println("面差不多了.....");
this.addCondiment();
System.out.println("调好味,起锅.....");
}
}
运用
里氏替换原则个人最经典用法就是JDK里面的集合体系。这里以List集合体系为例子。来看下List集合UML图:
Colleciton接口:定制集合操作规范
核心方法: size contains iterator add remove clear
List接口:继承至Colleciton接口,在Collection基础上补充了List集合规范
核心方法:size contains iterator add remove clear get listIterator
AbstractList抽象类: 实现List接口,实现了list集合公共的方法,子类List有差异的方法保持原状(还是抽象方法)。看源码会发现都是一些空实现, 并没有太多list相关操作逻辑。
空实现方法:add set remove
真实实现方法:clear iterator listIterator
没实现保留抽象方法:get
ArrayList类:继承AbstractList抽象类,继承了list集合公共方法, 拓展了符合ArrayList特点的list集合操作方法。
重写方法:add get contains remove clear iterator
LinkedList类:继承AbstractList抽象类,继承了list集合公共方法, 拓展了符合LinkedList特点的list集合操作方法。
写在最后:依赖倒转原则、里氏替换原则2大原则最佳实践就是JDK集合体系,接口负责对集合行为做统一抽象, 抽象类对不同集合行为进行隔离与分化(拆分出list set map集合),定制不同集合操作规范,再由子类完成具体实践,完美。
这实现带来什么好处?我说一个大家可以细细品:JDK8后集合接口升级
总结
里氏替换原则使用过程中要明确的:
1>父类定义为抽象类,负责公共方法实现与方法规则定制
2>子类重写父类要求子类重写的方法
3>能用父类对象的地方,必须做到也能使用子类(面向继承多态)。
4>没有明显的子父类关系,不建议使用继承。(继承让代码体系变复杂,建议使用组合方式)
最后
以上就是强健龙猫为你收集整理的浅谈设计模式-里氏替换原则的全部内容,希望文章能够帮你解决浅谈设计模式-里氏替换原则所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复