举例解析Java的设计模式编程中里氏替换原则的意义

作者:卡奴达摩 时间:2021-06-30 18:37:17 

里氏替换原则,OCP作为OO的高层原则,主张使用“抽象(Abstraction)”和“多态(Polymorphism)”将设计中的静态结构改为动态结构,维持设计的封闭性。“抽象”是语言提供的功能。“多态”由继承语义实现。

里氏替换原则包含以下4层含义:

  1. 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。

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

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

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

现在我们可以对以上四层含义进行讲解。

子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法

在我们做系统设计时,经常会设计接口或抽象类,然后由子类来实现抽象方法,这里使用的其实就是里氏替换原则。子类可以实现父类的抽象方法很好理解,事实上,子类也必须完全实现父类的抽象方法,哪怕写一个空方法,否则会编译报错。

里氏替换原则的关键点在于不能覆盖父类的非抽象方法。父类中凡是已经实现好的方法,实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些规范,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。

在面向对象的设计思想中,继承这一特性为系统的设计带来了极大的便利性,但是由之而来的也潜在着一些风险。下面举例来说明继承的风险,我们需要完成一个两数相减的功能,由类A来负责。


class A{
 public int func1(int a, int b){
   return a-b;
 }
}

public class Client{
 public static void main(String[] args){
   A a = new A();
   System.out.println("100-50="+a.func1(100, 50));
   System.out.println("100-80="+a.func1(100, 80));
 }
}

 运行结果:


100-50=50
100-80=20

        后来,我们需要增加一个新的功能:完成两数相加,然后再与100求和,由类B来负责。即类B需要完成两个功能:
两数相减。
两数相加,然后再加100。
        由于类A已经实现了第一个功能,所以类B继承类A后,只需要再完成第二个功能就可以了,代码如下:


class B extends A{
 public int func1(int a, int b){
   return a+b;
 }

public int func2(int a, int b){
   return func1(a,b)+100;
 }
}

public class Client{
 public static void main(String[] args){
   B b = new B();
   System.out.println("100-50="+b.func1(100, 50));
   System.out.println("100-80="+b.func1(100, 80));
   System.out.println("100+20+100="+b.func2(100, 20));
 }
}

类B完成后,运行结果:


100-50=150
100-80=180
100+20+100=220

        我们发现原本运行正常的相减功能发生了错误。原因就是类B在给方法起名时无意中重写了父类的方法,造成所有运行相减功能的代码全部调用了类B重写后的方法,造成原本运行正常的功能出现了错误。在本例中,引用基类A完成的功能,换成子类B之后,发生了异常。在实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。

标签:Java,设计模式
0
投稿

猜你喜欢

  • java中的前++和后++的区别示例代码详解

    2023-09-01 10:15:01
  • 关于Spring源码是如何解决Bean的循环依赖

    2023-09-16 14:15:17
  • SpringBoot如何使用ApplicationContext获取bean对象

    2023-06-28 20:36:55
  • C# 填充Excel图表、图例背景色的实例代码

    2023-08-06 04:01:31
  • Java 7大常见排序方法实例详解

    2022-01-09 05:16:46
  • Java取整与四舍五入

    2021-07-25 01:07:32
  • 浅谈Java中的Filter过滤器

    2023-07-23 10:00:08
  • C# Winform实现自定义漂亮的通知效果

    2021-08-10 08:15:29
  • Java SWT中常见弹出框实例总结

    2023-08-22 00:42:22
  • 异常try catch的常见四类方式(案例代码)

    2023-11-10 23:18:00
  • Android线性布局与相对布局的实现

    2021-07-26 09:08:01
  • C#如何自定义multipart/form-data的解析器

    2023-12-04 18:59:03
  • 利用Distinct()内置方法对List集合的去重问题详解

    2023-01-31 00:45:30
  • C#实现目录跳转(TreeView和SplitContainer)的示例代码

    2023-07-31 11:07:59
  • Android持久化技术之SharedPreferences存储实例详解

    2023-03-25 03:17:10
  • 通过面试题解析 Java 类加载机制

    2022-08-13 12:49:16
  • java和c#使用hessian通信的方法

    2021-12-12 22:03:46
  • 详解Android Flutter中SliverAppBar的使用教程

    2023-06-23 12:11:27
  • 浅谈C#设计模式之代理模式

    2023-01-23 14:30:59
  • javac -encoding 用法详解

    2022-06-28 08:58:08
  • asp之家 软件编程 m.aspxhome.com