六大设计原则之里氏替换原则(LSP)

一、SOLID

设计模式的六大原则有:

  • Single Responsibility Principle:单一职责原则
  • Open Closed Principle:开闭原则
  • Liskov Substitution Principle:里氏替换原则
  • Law of Demeter:迪米特法则
  • Interface Segregation Principle:接口隔离原则
  • Dependence Inversion Principle:依赖倒置原则

把这六个原则的首字母联合起来(两个 L 算做一个)就是 SOLID (solid,稳定的),其代表的含义就是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计。下面我们来看一下里氏替换原则。
设计模式六大原则(SOLID)

二、里氏替换原则定义

【所有引用基类的地方必须能透明地使用其子类的对象】

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

三、里氏替换原则弥补继承的缺陷

氏替换原则的意思是,所有基类在的地方,都可以换成子类,程序还可以正常运行。这个原则是与面向对象语言的继承特性密切相关的。

在学习java类的继承时,我们知道继承有一些优点:

  • 子类拥有父类的所有方法和属性,从而可以减少创建类的工作量。
  • 提高了代码的重用性。
  • 提高了代码的扩展性,子类不但拥有了父类的所有功能,还可以添加自己的功能。

但有优点也同样存在缺点:

  • 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。
  • 降低了代码的灵活性。因为继承时,父类会对子类有一种约束。
  • 增强了耦合性。当需要对父类的代码进行修改时,必须考虑到对子类产生的影响。

如何扬长避短呢?方法是引入里氏替换原则。

四、里氏替换原则对继承进行了规则上的约束

里氏替换原则对继承进行了规则上的约束,这种约束主要体现在四个方面:

  • 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。

  • 子类中可以增加自己特有的方法。

  • 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比- 父类方法的输入参数更宽松。(即只能重载不能重写)

  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

下面对以上四个含义进行详细的阐述

4.1、子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法

在我们做系统设计时,经常会设计接口或抽象类,然后由子类来实现抽象方法,这里使用的其实就是里氏替换原则。若子类不完全对父类的方法进行实例化,那么子类就不能被实例化,那么这个接口或抽象类就毫无存在的意义了。

里氏替换原则规定,子类不能覆写父类已实现的方法。父类中已实现的方法其实是一种已定好的规范和契约,如果我们随意的修改了它,那么可能会带来意想不到的错误。下面举例说明一下子类覆写了父类方法带来的后果。

public class Father {

    public void fun(int a, int b) {
        System.out.println(a + "+" + b + "=" + (a + b));
    }
}

public class Son extends Father {

    @Override
    public void fun(int a, int b) {
        System.out.println(a + "-" + b + "=" + (a - b));
    }
}


public class Client {

    public static void main(String[] args) {
        Father father = new Father();
        father.fun(1, 2);

        // 父类存在的地方,可以用子类替代
        // 子类Son替代父类Father
        System.out.println("子类替代父类后的运行结果");
                Son son = new Son();
                son.fun(1, 2);
    }
}

运行结果:

1+2=3
子类替代父类后的运行结果
1-2=-1

我们想要的结果是“1+2=3”。可以看到,方法重写后结果就不是了我们想要的结果了,也就是这个程序中子类B不能替代父类A。这违反了里氏替换原则,从而给程序造成了错误。

我们可以给父类的非抽象(已实现)方法加final修饰,这样就在语法层面控制了父类非抽象方法被子类重写而违反里氏替换原则。

有时候父类有多个子类(Son1、Son2),但在这些子类中有一个特例(Son2)。要想满足里氏替换原则,又想满足这个子类的功能时,有的伙伴可能会修改父类(Father)的方法。但是,修改了父类的方法又会对其他的子类造成影响,产生更多的错误。这是怎么办呢?我们可以为这个特例(Son2)创建一个新的父类(Father2),这个新的父类拥有原父类的部分功能(Father2并不继承Father,而是持有Father的一个引用,组合Father,调用Father里的功能),又有不同的功能。这样既满足了里氏替换原则,又满足了这个特例的需求。

4.2、子类中可以增加自己特有的方法

这个很容易理解,子类继承了父类,拥有了父类和方法,同时还可以定义自己有,而父类没有的方法。这是在继承父类方法的基础上进行功能的扩展,符合里氏替换原则。

4.3 、当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松

先看一段代码:

public class Father {
    public void fun(HashMap map){
        System.out.println("父类被执行...");
    }
}

public class Son extends Father {
    public void fun(Map map){
        System.out.println("子类被执行...");
    }
}

public class Client {

    public static void main(String[] args) {
        System.out.print("父类的运行结果:");
        Father father=new Father();
        HashMap map=new HashMap();
        father.fun(map);
        
        //父类存在的地方,可以用子类替代
        //子类B替代父类A
        System.out.print("子类替代父类后的运行结果:");
        Son sun=new Son();
        son.fun(map);
    }
}

运行结果:

父类的运行结果:父类被执行...
子类替代父类后的运行结果:父类被执行...

我们应当主意,子类并非重写了父类的方法,而是重载了父类的方法。因为子类和父类的方法的输入参数是不同的。子类方法的参数Map比父类方法的参数HashMap的范围要大,所以当参数输入为HashMap类型时,只会执行父类的方法,不会执行子类的重载方法。这符合里氏替换原则。

但如果我将子类方法的参数范围缩小会怎样?看代码:

public class Father {
    public void fun(Map map){
        System.out.println("父类被执行...");
    }
}

public class Son extends Father {
    public void fun(HashMap map){
        System.out.println("子类被执行...");
    }
}


public class Client {

    public static void main(String[] args) {
        System.out.print("父类的运行结果:");
        Father father=new Father();
        HashMap map=new HashMap();
        father.fun(map);
        
        //父类存在的地方,可以用子类替代
        //子类B替代父类A
        System.out.print("子类替代父类后的运行结果:");
        Son son=new Son();
        son.fun(map);
    }
}

运行结果:

父类的运行结果:父类被执行...
子类替代父类后的运行结果:子类被执行...

在父类方法没有被重写的情况下,子方法被执行了,这样就引起了程序逻辑的混乱。所以子类中方法的前置条件必须与父类中被覆写的方法的前置条件相同或者更宽松。

4.4、当子类的方法实现父类的(抽象)方法时,方法的后置条件(即方法的返回值)要比父类更严格
public abstract class Father {
    public abstract Map fun();
}

public class Son extends Father {
    @Override
    public HashMap fun() {
        System.out.println("子类被执行...");
        return null;
    }
}

public class Client {

    public static void main(String[] args) {
        Father father=new Son();
        father.fun();
    }
}

运行结果:

子类被执行...

注意:是实现父类的抽象方法,而不是父类的非抽象(已实现)方法,不然就违法了第一条。

若在继承时,子类的方法返回值类型范围比父类的方法返回值类型范围大,在子类重写该方法时编译器会报错。(Java语法)

参考:

最后给大家送波福利

阿里云折扣快速入口

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容