设计模式七大原则——里氏替换原则

一、面向对象中继承性的思考和说明

  (1)继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。

  (2)继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低, 增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障

  (3)问题提出:在编程中,如何正确的使用继承?

      答案就是遵循里氏替换原则

二、基本介绍

  (1)背景介绍

  在面向对象的程序设计中,里氏替换原则(Liskov Substitution principle)是对子类型的特别定义。它由麻省理工学院电子电气与计算机科学系教授芭芭拉·利斯科夫(Barbara Liskov)在1987年在一次会议上名为“数据的抽象与层次”的演说中首先提出。里氏替换原则的内容可以描述为:“派生类(子类)对象可以在程式中代替其基类(超类)对象。” 以上内容并非利斯科夫的原文,而是译自罗伯特·马丁(Robert Martin)对原文的解读。芭芭拉·利斯科夫与周以真(Jeannette Wing)在1994年发表论文并提出以上的Liskov代换原则。

  (2)具体表述

  如果对每个类型为 T1 的对象 O1,都有类型为 T2 的对象 O2,使得以 T1 定义的所有程序 P 在所有的对象 O1 都代换成 O2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。

  换句话说,所有引用基类的地方必须能透明地使用其子类的对象。

  在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法

  里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合、组合、依赖来解决问题

三、应用实例

  先来看一段程序代码:

 1 package edu.hbeu.principle.liskov;
 2 
 3 public class Liskov1 {
 4     public static void main(String[] args) {
 5         A a = new A();
 6         System.out.println("11-3=" + a.func1(11, 3));
 7         System.out.println("1-8=" + a.func1(1, 8));
 8 
 9         System.out.println("=======================");
10         B b = new B();
11         System.out.println("11-3=" + b.func1(11, 3));//本意是要求出11-3的结果
12         System.out.println("1-8=" + b.func1(1, 8));//本意是要求出1-8的结果
13         System.out.println("11+3+9=" + b.func2(11, 3));
14     }
15 }
16 
17 
18 class A {
19     //返回两数之差
20     public int func1(int num1, int num2) {
21         return num1 - num2;
22     }
23 }
24 
25 //增加了一个新功能:完成两个数相加,然后和9求和
26 class B extends A {
27     //重写了A类的方法,这种重写可能是无意识的
28     @Override
29     public int func1(int num1, int num2) {
30         return num1 + num2;
31     }
32 
33     public int func2(int a, int b) {
34         return func1(a, b) + 9;
35     }
36 }

  运行结果:

  

   分析:

    由于类B继承类A之后重写了类A的方法,导致其功能发生变化,最终导致错误的结果,这种重写可能是无意识的,但是在项目开发中可能经常会碰到,所以我们需要遵循里氏替换原则来对继承进行解耦

  解决方案:

  (1)我们发现原来运行正常的相减功能发生了错误。原因就是类B无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候

  (2)通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合、组合等关系代替。

  

  代码实现:

    这里我们采用组合关系来代替继承,类图如下:

    

  具体代码:

 1 public class Liskov {
 2     public static void main(String[] args) {
 3         A a = new A();
 4         System.out.println("11-3=" + a.func1(11, 3));
 5         System.out.println("1-8=" + a.func1(1, 8));
 6 
 7         System.out.println("=======================");
 8         B b = new B();
 9         //B类不再继承A类,因此调用者,不再认为func1是求减法,调用完成的功能就会很明确
10         System.out.println("11+3=" + b.func1(11, 3));//本意是求11+3
11         System.out.println("1+8=" + b.func1(1, 8));//本意是求1+8
12         System.out.println("11+3+9=" + b.func2(11, 3));
13 
14         //使用组合,仍然可以使用到A的相关方法
15         System.out.println("11-3=" + b.func3(11, 3));
16     }
17 }
18 
19 //创建一个更加基础的类
20 class Base {
21     //把更加基础的方法和成员写到Base
22 }
23 
24 class A extends Base {
25     //返回两数之差
26     public int func1(int num1, int num2) {
27         return num1 - num2;
28     }
29 }
30 
31 class B extends Base {
32     //B类中使用A类的方法,使用组合的关系
33     private A a = new A();
34 
35     public int func1(int a, int b) {
36         return a + b;
37     }
38 
39     public int func2(int a, int b) {
40         return func1(a, b) + 9;
41     }
42 
43     //仍然使用A类中的方法
44     public int func3(int a, int b) {
45         return this.a.func1(a, b);
46     }
47 }

  运行结果:

  

   总结:使原有的父类和子类都继承一个更通俗的基类,并用组合关系来代替原有的继承关系来遵循里氏替换原则,降低了代码的耦合度,解决了使用继承特性时对代码造成的侵入

 

posted @ 2021-01-27 22:13  孤云jh  阅读(167)  评论(0编辑  收藏  举报