面向对象设计原则之里式替换原则

目录

  • 定义
  • 作用
  • 实现方法
  • 代码示例

定义

里式替换原则(Liskov Substitution Principle),缩写为LSP,这个原则最早是在1986年由麻省理工学院计算机科学实验室的Barbara Liskov提出,她是这么描述这条原则的:

If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。

在1996年,Robert C.Martin(罗伯特C.马丁,鲍勃大叔)在他的SOLID(S单一职责 O开闭原则 L里式替换原则 I接口隔离原则 D依赖倒置原则)原则中,重新描述了这个原则:

Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。

综合两者描述,我们将这条原则用中文描述出来为:程序中的对象可以在不改变程序正确性的前提下被它的子类所替换,即:子类可以替换任何父类能够出现的地方,并且经过替换后,代码还能正确工作。

根据LSP的定义,如果在程序中出现使用instanceof、强制类型转换或者函数覆盖,很可能意味着是对LSP的破坏。

里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

作用

  • 里氏替换原则是实现开闭原则的重要方式之一。它克服了继承中重写父类造成的可复用性变差的缺点。它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。
  • 加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险

实现方法

里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。根据上述理解,对里氏替换原则的定义可以总结如下:

  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  2. 子类中可以增加自己特有的方法。
  3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松。
  4. 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等。

代码示例

以鸵鸟是不是鸟为例,鸵鸟是一种鸟,但是不会飞。

1.我们创建鸟类,有个飞行的方法,然后鸵鸟和麻雀分别继承鸟类,但是鸵鸟由于不会飞,所以需要重写父类的飞行方法。

package will.tools.design.lsp.a;
 
public class Birds {
 
    public void fly() {
        System.out.println("I am flying");
    }
}
 
package will.tools.design.lsp.a;
 
public class Sparrow extends Birds {
    @Override
    public void fly() {
        super.fly();
    }
}
 
package will.tools.design.lsp.a;
 
public class Ostrich extends Birds {
 
    @Override
    public void fly() {
        System.out.println("i can not fly");
    }
}

这时我们看到,在鸵鸟的类中,我们重写了飞行方法。我们测试一下,

public static void main(String[] args){
        Birds birds = new Birds();
        birds.fly();
 
        Birds sparrow = new Sparrow();
        sparrow.fly();
 
        Birds ostrich = new Ostrich();
        ostrich.fly();
    }

I am flying
I am flying
i can not fly

我们发现,创建父类对象new Birds的地方,我们用Sparrow来替换,没有问题,但是用Ostrich来替换,运行结果就已经变了。这其实就是我们设计鸵鸟类的时候,继承鸟类这里已经违反了里式替换原则。

怎么修改呢,一种方式我们认为鸵鸟和鸟还是有继承关系存在的(鸵鸟是鸟),把无法继承的飞行的行为抽象为接口,Birds类中不再有飞行方法。麻雀和鸵鸟都继承鸟的基础信息,但是根据业务规则,麻雀实现飞行的接口,鸵鸟不实现飞行接口。另一种方式是,我们认为鸵鸟和鸟不再有继承关系(鸵鸟不是鸟),这是我们抽象一个动物类,有一个move方法,鸟类和鸵鸟都继承动物类(鸟类的移动是飞,鸵鸟的移动是跑,但都是move),麻雀继承鸟类。

我们以第二种方式类修改代码如下

package will.tools.design.lsp.b;
 
public class Animal {
    public void move() {
        System.out.println("I am moving");
    }
}
 
package will.tools.design.lsp.b;
 
public class Birds extends Animal{
    @Override
    public void move() {
        super.move();
    }
}
 
package will.tools.design.lsp.b;
 
public class Ostrich extends Animal {
    @Override
    public void move() {
        super.move();
    }
}
 
package will.tools.design.lsp.b;
 
public class Sparrow extends Birds{
    @Override
    public void move() {
        super.move();
    }
}
 
    public static void main(String[] args){
        Animal animal = new Animal();
        animal.move();
 
        Animal birds = new Birds();
        birds.move();
 
        Animal sparrow = new Sparrow();
        sparrow.move();
 
        Animal ostrich = new Ostrich();
        ostrich.move();
    }

这是我们运行验证,结果为:

I am moving
I am moving
I am moving
I am moving

这时,我们出现父类对象new Animal()的地方,使用 Birds,Sparrow,Ostrich 来替换,运行结果都是一致的,都没有问题

你可能感兴趣的:(面向对象设计,java,设计模式)