我是靠谱客的博主 强健龙猫,最近开发中收集的这篇文章主要介绍浅谈设计模式-里氏替换原则,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

书接上回,本篇继续讲一下设计模式六大原则(有些书认为是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>没有明显的子父类关系,不建议使用继承。(继承让代码体系变复杂,建议使用组合方式)

最后

以上就是强健龙猫为你收集整理的浅谈设计模式-里氏替换原则的全部内容,希望文章能够帮你解决浅谈设计模式-里氏替换原则所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(30)

评论列表共有 0 条评论

立即
投稿
返回
顶部