zoukankan      html  css  js  c++  java
  • 面向对象五大原则-----里氏代换原则

      什么是里氏代换原则 

      里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新

    的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

      简单的理解为一个软件实体如果使用的是一个父类,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,软件里面,把父类都替换成它的子类,程序的行为没有变化。

      但是反过来的代换却不成立,里氏代换原则(Liskov Substitution Principle):一个软件实体如果使用的是一个子类的话,那么它不能适用于其父类。

       举个例子解释一下这个概念

      先创建一个Person类

    1 public class Person {
    2     public void display() {
    3         System.out.println("this is person");
    4     }
    5 }

      再创建一个Man类,继承这个Person类

    1 public class Man extends Person {
    2 
    3     public void display() {
    4         System.out.println("this is man");
    5     }
    6     
    7 }

      运行一下

     1 public class MainClass {
     2     public static void main(String[] args) {
     3         Person person = new Person();//new一个Person实例
     4         display(person);
     5         
     6         Person man = new Man();//new一个Man实例
     7         display(man);
     8     }
     9     
    10     public static void display(Person person) {
    11         person.display();
    12     }
    13 }

      可以看到

      运行没有影响,符合一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类和子类对象的区别这句概念,这也就是java中的多态。

      而反之,一个子类的话,那么它不能适用于其父类,这样,程序就会报错

     1 public class MainClass {
     2     public static void main(String[] args) {
     3         Person person = new Person();
     4         display(person);//这里报错
     5         
     6         Man man = new Man();
     7         display(man);
     8     }
     9     
    10     public static void display(Man man) {//传入一个子类
    11         man.display();
    12     }
    13 }

      继续再举一个很经典的例子,正方形与长方形是否符合里氏代换原则,也就是说正方形是否是长方形的一个子类,

       以前,我们上学都说正方形是特殊的长方形,是宽高相等的长方形,所以我们认为正方形是长方形的子类,但真的是这样吗?

      

      从图中,我们可以看到长方形有两个属性宽和高,而正方形则只有一个属性边长

      所以,用代码如此实现

     1 //长方形
     2 public class Changfangxing{
     3     private long width;
     4     private long height;
     5     
     6     public long getWidth() {
     7         return width;
     8     }
     9     public void setWidth(long width) {
    10         this.width = width;
    11     }
    12     public long getHeight() {
    13         return height;
    14     }
    15     public void setHeight(long height) {
    16         this.height = height;
    17     }
    18 }
     1 //正方形
     2 public class Zhengfangxing{
     3     private long side;
     4 
     5     public long getSide() {
     6         return side;
     7     }
     8 
     9     public void setSide(long side) {
    10         this.side = side;
    11     }
    12 }

      可以看到,它们的结构根本不同,所以正方形不是长方形的子类,所以长方形与正方形之间并不符合里氏代换原则。

      当然我们也可以强行让正方形继承长方形

     1 //正方形
     2 public class Zhengfangxing extends Changfangixng{
     3     private long side;
     4 
     5     public long getHeight() {
     6         return this.getSide();
     7     }
     8 
     9     public long getWidth() {
    10         return this.getSide();
    11     }
    12 
    13     public void setHeight(long height) {
    14         this.setSide(height);
    15     }
    16 
    17     public void setWidth(long width) {
    18         this.setSide(width);
    19     }
    20 
    21     public long getSide() {
    22         return side;
    23     }
    24 
    25     public void setSide(long side) {
    26         this.side = side;
    27     }
    28 }

       这个样子,编译器是可以通过的,也可以正常使用,但是这样就符合里氏代换原则了吗,肯定不是的。

      我们不是为了继承而继承,只有真正符合继承条件的情况下我们才去继承,所以像这样为了继承而继承,强行实现继承关系的情况也是不符合里氏代换原则的。

      但这是为什么呢?,我们运行一下

     1 public class MainClass {
     2     public static void main(String[] args) {
     3         Changfangxing changfangxing = new Changfangxing();
     4         changfangxing.setHeight(10);
     5         changfangxing.setWidth(20);
     6         test(changfangxing);
     7         
     8         Changfangxing zhengfangxing = new Zhengfangxing();
     9         zhengfangxing.setHeight(10);
    10         test(zhengfangxing);
    11     }
    12     
    13     public static void test(Changfangxing changfangxing) {
    14         System.out.println(changfangxing.getHeight());
    15         System.out.println(changfangixng.getWidth());
    16     }
    17 }

      结果:

      我们忽然发现,很正常啊,为什么不可以,但是我们继续修改

     1 public class MainClass {
     2     public static void main(String[] args) {
     3         Changfangxing changfangxing = new Changfangxing();
     4         changfangxing.setHeight(10);
     5         changfangxing.setWidth(20);
     6         resize(changfangxing);
     7         
     8         Changfangxing zhengfangxing = new Zhengfangxing();
     9         zhengfangxing.setHeight(10);
    10         resize(zhengfangxing);
    11     }
    12     
    13     public static void test(Changfangxing changfangxing) {
    14         System.out.println(changfangxing.getHeight());
    15         System.out.println(changfangxing.getWidth());
    16     }
    17     
    18     public static void resize(Changfangxing changfangxing) {
    19         while(changfangxing.getHeight() <= changfangxing.getWidth()) {
    20             changfangxing.setHeight(changfangxing.getHeight() + 1);
    21             test(changfangxing);
    22         }
    23     }
    24 }

      当长方形运行时,可以正常运行,而正方形则会造成死循环,所以这种继承方式不一定恩能够适用于所有情况,所以不符合里氏代换原则。

      还有一种形式,我们抽象出一个四边形接口,让长方形和正方形都实现这个接口

    1 public interface Sibianxing {
    2     public long getWidth();
    3     public long getHeight();
    4 }
     1 public class Changfangxing implements Sibianxing{
     2     private long width;
     3     private long height;
     4     
     5     public long getWidth() {
     6         return width;
     7     }
     8     public void setWidth(long width) {
     9         this.width = width;
    10     }
    11     public long getHeight() {
    12         return height;
    13     }
    14     public void setHeight(long height) {
    15         this.height = height;
    16     }
    17 }
     1 package com.ibeifeng.ex3;
     2 
     3 public class Zhengfangxing implements Sibianxing{
     4     private long side;
     5 
     6     public long getHeight() {
     7         return this.getSide();
     8     }
     9 
    10     public long getWidth() {
    11         return this.getSide();
    12     }
    13 
    14     public void setHeight(long height) {
    15         this.setSide(height);
    16     }
    17 
    18     public void setWidth(long width) {
    19         this.setSide(width);
    20     }
    21 
    22     public long getSide() {
    23         return side;
    24     }
    25 
    26     public void setSide(long side) {
    27         this.side = side;
    28     }
    29 }

      运行

     1 public class MainClass {
     2     public static void main(String[] args) {
     3         Changfangxing changfangxing = new Changfangxing();
     4         changfangxing.setHeight(10);
     5         changfangxing.setWidth(20);
     6         test(changfangxing);
     7         
     8         Zhengfangxing zhengfangxing = new Zhengfangxing();
     9         zhengfangxing.setHeight(10);
    10         test(zhengfangxing);
    11     }
    12     
    13     public static void test(Sibianxing sibianxing) {
    14         System.out.println(sibianxing.getHeight());
    15         System.out.println(sibianxing.getWidth());
    16     }
    17 }

      对于长方形和正方形,取width和height是它们共同的行为,但是给width和height赋值,两者行为不同,因此,这个抽象的四边形的类只有取值方法,没有赋值方法。上面的例子中那个方法只会适用于不同的子类,LSP也就不会被破坏。

      注意事项

      在进行设计的时候,尽量从抽象类继承,而不是从具体类继承。如果从继承等级树来看,所有叶子节点应当是具体类,而所有的树枝节点应当是抽象类或者接口。当然这个只是一个一般性的指导原则,使用的时候还要具体情况具体分析。

  • 相关阅读:
    笨方法学python中执行argv提示ValueError: not enough values to unpack (expected 4, got 1)
    VMware workstation安装
    Redis bigkey分析
    MySQL drop table 影响及过程
    MySQL 大表硬连接删除
    ES elasticsearch 各种查询
    ES elasticsearch 各种聚合
    ES elasticsearch 聚合统计
    ES elasticsearch 实现 count单字段,分组取前多少位,以地理位置中心进行统计
    MySQL行溢出、varchar最多能存多少字符
  • 原文地址:https://www.cnblogs.com/xiaobai1226/p/8664621.html
Copyright © 2011-2022 走看看