• 北风设计模式课程---33、里氏代换原则


    北风设计模式课程---33、里氏代换原则

    一、总结

    一句话总结:

    视频教程网上一定能找到做好笔记的博客,很大几率都不需要自己做笔记。比如北风设计模式课程,https://www.cnblogs.com/xiaobai1226/p/8664621.html,这个博客就做好了完整的笔记。

    1、里氏代换原则经典实例?

    正方形是长方形么?:正方形继承长方形之后,可以骗过java编译器,但是while(changfangxing.getHeight() <= changfangxing.getWidth()){changfangxing.setHeight(changfangxing.getHeight() + 1);}却没办法
    public static void resize(Changfangxing changfangxing) {
      while(changfangxing.getHeight() <= changfangxing.getWidth()) {
          changfangxing.setHeight(changfangxing.getHeight() + 1);
          test(changfangxing);
      }
    }

    2、里氏代换原则 如何理解?

    1、能放父类的位置一定能放子类,反之不一定成立。
    2、java编译器很好骗(父类可以指向子类引用,编译器中继承即可),但是具体的业务逻辑是否符合里氏代换原则,这个看场景

    3、什么是里氏代换原则?

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


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

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

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

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

    4、类设计注意?

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

    二、面向对象五大原则-----里氏代换原则

    转自或参考:面向对象五大原则-----里氏代换原则
    https://www.cnblogs.com/xiaobai1226/p/8664621.html

      什么是里氏代换原则 

      里氏代换原则(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也就不会被破坏。

      注意事项

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

     
  • 相关阅读:
    PrimeNG之Validation
    PrimeNG之FileUpload
    PrimeNG之DataTable
    PrimeNG之TreeTable
    AngularJS实现可伸缩的页面切换
    ng2-table
    【转】前端框架天下三分:Angular React 和 Vue的比较
    【转】AngularJS动态生成div的ID
    Emprie 使用基础笔记
    开源沙箱CuckooSandbox 介绍与部署
  • 原文地址:https://www.cnblogs.com/Renyi-Fan/p/11106889.html
Copyright © 2020-2023  润新知