22

面向对象的7种设计原则(5)-里氏代换原则

 3 years ago
source link: http://likeyou.cool/2020/07/05/mian-xiang-dui-xiang-de-7-chong-she-ji-yuan-ze-5-li-shi-dai-huan-yuan-ze/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

原则

第一点

子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。

class Foo {
    public void cal(int num1, int num2) {
        int value = num1 + num2;
        System.out.println("父类计算结果: " + value);
    }
}

class Son extends Foo {
    public void cal(int num1, int num2) {
        int value = num1 - num2;
        System.out.println("子类计算结果:" + value);
    }
}

class Cal{
    public static void main(String[] args) {
        Foo foo = new Foo();
        foo.cal(2,1);
        Son son = new Son();
        son.cal(2,1);
    }
}

在类的继承中,我们的父类定义好的方法,并不会强制要求其子类必须完全遵守该方法的实现规则。子类是可以修改它继承自父类的任意方法的。

在本例中,父类的本意是想要定义一个两数相加的方法,但是子类继承该方法后却修改为减法,并且也成功了。子类这样操作后,会对整个继承体系造成破坏。当你想把使用父类的地方替换为其子类时,会发现原来的正常的功能现在出现问题了。

第二点

当子类需要重载父类中的方法的时候,子类方法的形参(入参)要比父类方法输入的参数更宽松(范围更广)。

class Foo {
    public void method(List arrayList) {
        System.out.println("父类方法执行");
    }
}

class Son extends Foo {
    public void method(ArrayList list) {
        System.out.println("子类方法执行" );
    }
}

class Cal{
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        Foo foo = new Foo();
        Son son = new Son();
        System.out.println("使用父类对象调用的结果:");
        foo.method(list);
        System.out.println("将父类对象替换为子类对象调用结果");
        son.method(list);
    }
}

//输出
使用父类对象调用的结果:
父类方法执行
将父类对象替换为子类对象调用结果
子类方法执行

我们的本意是希望对象替换后还执行原来的方法的,可结果却发生变化了。

修改

class Foo {
    public void method(ArrayList arrayList) {
        System.out.println("父类方法执行");
    }
}

class Son extends Foo {
    //重载了父类的method,并且方法入参比父类的入仓范围更广
    public void method(List list) {
        System.out.println("子类方法执行" );
    }
}

第三点

重写或者实现父类方法的时候,方法的返回值可以被缩小,但是不能放大。

正例:

class Foo {
    public List getList() {
        return new ArrayList();
    }
}

class Son extends Foo {
    public ArrayList getList() {
        return new ArrayList();
    }
}

反例:

class Foo {
    public ArrayList getList() {
        return new ArrayList();
    }
}

class Son extends Foo {
    public List getList() {
        return new ArrayList();
    }
}

如果我们试图在子类中放大,重写或实现来自父类方法的返回值时,代码会报错,连基本的编译器都无法通过。

第四点

子类可以拥有自己独特的方法或属性

class Foo {
    public void cal(int num1, int num2) {
        int value = num1 + num2;
        System.out.println("父类计算结果: " + value);
    }
}

class Son extends Foo {
    public void cal(int num1, int num2) {
        int value = num1 - num2;
        System.out.println("子类计算结果:" + value);
    }
    public void cal2(int num1, int num2) {
        int value = num1 + num2 +num2;
        System.out.println("子类计算结果:" + value);
    }
}

总结

通过上面的描述相信大家都对里氏替换原则有了一个基本的概念,其实它就是告诉我们在继承中需要注意什么问题和遵守什么规则。

然而在实际开发中我们在很多时候还是会违背该原则的,虽然表面上没有什么特别大的问题,但是这样做会大大增加代码的出错率。我们编写代码时不光要考虑怎么实现该功能,程序的健壮性和后期的扩展以及移植都是需要考虑到的。只有这样做才可以使我们的程序更加优秀。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK