zoukankan      html  css  js  c++  java
  • 设计模式七大原则——里氏替换原则

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

      (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 }

      运行结果:

      

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

  • 相关阅读:
    day25 初始面向对象
    JavaScript中的apply()和call()
    JavaScript中的arguments详解
    测试使用MarkDown在博客园发布博客
    《Spring实战》 1-2
    总结: 《jQuery基础教程》 5-完结
    总结: 《jQuery基础教程》 1-4章
    做个计划
    Nginx与tomcat组合的简单使用
    利用 Dijit 组件框架打造丰富的用户界面
  • 原文地址:https://www.cnblogs.com/yijiahao/p/14337307.html
Copyright © 2011-2022 走看看