`
lh_kevin
  • 浏览: 43978 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

里氏代换原则

 
阅读更多

 

设计模式六大原则(2):里氏替换原则 (转载)

 

我们都知道面向对象有三大特性:封装、继承、多态。所以我们在实际开发过程中,子类在继承父类后,根据多态的特性,可能是图一时方便,经常任意重写父类的方法,那么这种方式会大大增加代码出问题的几率。比如下面场景:类C实现了某项功能F1。现在需要对功能F1作修改扩展,将功能F1扩展为F,其中F由原有的功能F1和新功能F2组成。新功能F由类C的子类C1来完成,则子类C1在完成功能F的同时,有可能会导致类C的原功能F1发生故障。这时候里氏替换原则就闪亮登场了。


什么是里氏替换原则
  前面说过的单一职责原则,从字面意思就很好理解,但是里氏替换原则就有点让人摸不着头脑。查过资料后发现原来这项原则最早是在1988年,由麻省理工学院一位姓里的女士(Liskov)提出来的。

  英文缩写:LSP (Liskov Substitution Principle)。
  严格的定义:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型。
  通俗的定义:所有引用基类的地方必须能透明地使用其子类的对象。
  更通俗的定义:子类可以扩展父类的功能,但不能改变父类原有的功能。


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

    子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
    子类中可以增加自己特有的方法。
    当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
    当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

现在我们可以对以上四层含义逐个讲解:

 

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

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

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

   在面向对象的设计思想中,继承这一特性为系统的设计带来了极大的便利性,但是由之而来的也潜在着一些风险。就像开篇所提到的那一场景一样,对于那种情况 最好遵循里氏替换原则,类C1继承类C时,可以添加新方法完成新增功能,尽量不要重写父类C的方法。否则可能带来难以预料的风险,比如下面一个简单的例子 还原开篇的场景:

 

public class C {
    public int func(int a, int b){
        return a+b;
    }
}
 
class C1 extends C{
    @Override
    public int func(int a, int b) {
        return a-b;
    }
}
 
class Client{
    public static void main(String[] args) {
        C c = new C1();
        System.out.println("2+1=" + c.func(2, 1));
    }
}

// 运行结果:2+1=1

 上面的运行结果明显是错误的。类C1继承C,后来需要增加新功能,类C1并没有新写一个方法,而是直接重写了父类C的func方法,违背里氏替换原则,引用父类的地方并不能透明的使用子类的对象,导致运行结果出错。

 

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

  在继承父类属性和方法的同时,每个子类也都可以有自己的个性,在父类的基础上扩展自己的功能。前面其实已经提到,当功能扩展时,子类尽量不要重写父类的方法,而是另写一个方法,所以对上面的代码加以更改,使其符合里氏替换原则,代码如下:

 

 

public class C {
    public int func(int a, int b){
        return a+b;
    }
}
 
class C1 extends C{
    public int func2(int a, int b) {
        return a-b;
    }
}
 
class Client{
    public static void main(String[] args) {
        C1 c = new C1();
        System.out.println("2-1=" + c.func2(2, 1));
    }
}

//运行结果:2-1=1

 

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

代码示例

 

public class Father {
    public void func(HashMap m){
        System.out.println("执行父类...");
    }
}
 

class Son extends Father{
    public void func(Map m){//方法的形参比父类的更宽松
        System.out.println("执行子类...");
    }
}
 
class Client{
    public static void main(String[] args) {
        Father f = new Son();//引用基类的地方能透明地使用其子类的对象。
        HashMap h = new HashMap();
        f.func(h);
    }
}

//运行结果:执行父类...

 注意Son类的func方法前面是不能加@Override注解的,因为否则会编译提示报错,因为这并不是重写(Override),而是重载(Overload),因为方法的输入参数不同。重写和重载的区别在Java面向对象详解一文中已作解释,此处不再赘述。

 

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

代码示例:

public abstract class Father {
    public abstract Map func();
}
 
class Son extends Father{
     
    @Override
    public HashMap func(){//方法的返回值比父类的更严格
        HashMap h = new HashMap();
        h.put("h", "执行子类...");
        return h;
    }
}
 
class Client{
    public static void main(String[] args) {
        Father f = new Son();//引用基类的地方能透明地使用其子类的对象。
        System.out.println(f.func());
    }
}

//执行结果:{h=执行子类...}

 总结

  继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了一些弊端,它增加了对象之间的耦合性。因此在系统设计时,遵循里氏替换原则,尽量避免子类重写父类的方法,可以有效降低代码出错的可能性。

 

 

分享到:
评论

相关推荐

    里氏代换原则案例程序LSP.zip

    里氏代换原则(Liskov Substitution Principle,简称LSP)是面向对象设计的基本原则之一,由芭芭拉·里科夫(Barbara Liskov)在1988年提出。该原则规定,子类必须能够替换它们的基类,并且在软件系统中不会产生任何...

    java的里氏代换原则

    里氏代换原则是面向对象设计的基本原则之一,源自于著名数学家贝努利·里氏的一个概念。在Java编程中,它对于理解和构建可扩展、健壮的软件系统至关重要。简单来说,里氏代换原则(LSP,Liskov Substitution ...

    里氏代换原则原文

    里氏代换原则是由麻省理工学院(MIT)计算机科学实验室的Liskov女士,在1987年的OOPSLA大会上发表的一篇文章《Data Abstraction and Hierarchy》里面提出来的,主要阐述了有关继承的一些原则,也就是什么时候应该...

    第二十八讲:基础三里氏代换原则

    **第二十八讲:基础三里氏代换原则** 在面向对象设计中,三里氏代换原则(Liskov Substitution Principle,简称LSP)是一个核心的设计原则,它由芭芭拉·里科夫(Barbara Liskov)在1988年提出。这个原则是类型继承...

    里氏替换原则Demo

    里氏替换原则(Liskov Substitution Principle,简称LSP)是面向对象设计的基本原则之一,它是由美国计算机科学家芭芭拉·里科夫(Barbara Liskov)提出的。这个原则强调的是在软件工程中,子类型必须能够替换它们的...

    c++里氏替换原则说明1

    C++ 里氏替换原则详解 C++ 里氏替换原则是指在面向对象编程中,子类对象可以替换其基类对象的使用,而不影响程序的逻辑正确性。这个原则是由Barbara Liskov提出的,她认为在子类继承基类时,子类对象应该可以替换...

    里氏代换原则_动力节点Java学院整理

    里氏代换原则是面向对象设计的基本原则之一,它的核心思想是确保软件系统的各个组件能够灵活替换而不影响整体系统的稳定性。这一原则由Barbara Liskov在1988年提出,因此也被称为Liskov替换原则(LSP)。在Java和...

    13丨软件设计的里氏替换原则:正方形可以继承长方形吗?.pdf

    里氏替换原则(Liskov Substitution Principle,简称LSP)是面向对象设计的基本原则之一,由Barbara Liskov在1988年提出。该原则指出,子类型必须能够替换它们的基类型而不影响程序的正确性。这意味着在软件系统中,...

    Java23种基本的设计模式整料整理学习源码示例zip

    里氏代换原则(Liskov Substitution Principle) 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的...

    24种设计模式介绍与6大设计原则

    而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科 3、依赖倒转原则(Dependence Inversion Principle) 这个是开闭原则的基础,具体内容:真...

    面向对象设计原则Java概要.ppt

    里氏代换原则  里氏代换原则定义 里氏代换原则(Liskov Substitution Principle, LSP)指出,子类必须能够替换它们的基类,而不影响程序的正确性。这意味着子类对象可以在任何基类对象被预期出现的地方进行替换,...

    设计模式Demo

    而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科 3、依赖倒转原则(Dependence Inversion Principle) 这个是开闭原则的基础,具体内容:真...

    软件设计的七大原则.doc

    软件设计的七大原则是软件设计的精髓所在,这七大原则分别是开闭原则、里氏代换原则、依赖倒置原则、接口隔离原则、合成/聚合复用原则、迪米特法则和抽象类原则。 一、 开闭原则(OCP) 开闭原则是指一个软件实体...

    java设计模式五大原则.doc

    Java 设计模式五大原则是软件设计中非常重要的原则,它们是:开-闭原则、里氏代换原则、依赖倒转原则、接口隔离原则和迪米特法则。这些原则都是软件设计的基石,它们指导我们如何设计出更加灵活、可维护、可扩展的...

    面向对象的设计的原则 电子版

    在面向对象设计中,有七个基本原则,即单一职责原则、开闭原则、里氏代换原则、依赖倒转原则、接口隔离原则、合成复用原则和迪米特法则。 1. 单一职责原则(SRP):一个类只负责一个功能领域中的相应职责。单一职责...

    软件设计的七大原则

    这些原则包括开闭原则、里氏代换原则、依赖倒置原则、接口隔离原则、合成/聚合复用原则、迪米特法则和抽象类原则。这些原则的目的是为了提高软件系统的灵活性、适应性、稳定性和延续性。 一、 开闭原则(OCP) 开...

    程序设计的七大原则

    按照里氏代换原则,答案是否定的,因为正方形的一些行为会违反长方形的行为(例如,正方形无法通过改变宽高而不等比例调整尺寸)。 #### 三、依赖倒置原则 (Dependence Inversion Principle) 依赖倒置原则的核心...

    软件工程阶段性报告原则

    以下主要探讨五个关键的设计原则:单一职责原则、开闭原则、里氏代换原则、依赖倒转原则以及合成复用原则。 1. 单一职责原则(Single Responsibility Principle, SRP) 该原则强调一个类或模块应只有一个引起其变化...

Global site tag (gtag.js) - Google Analytics