设计模式(4)之七大原则之里氏替换

一、定义

        1.1关于OOP的一些思考

  1. 继承包含这么一层含义:父类凡是已经实现好的方法,实际上是设定规范和契约,虽然他不强制所有的子类必须遵循这些契约,但是如果子类对这些方法进行任意修改,就会对这个继承体系造成破坏。
  2. 继承给程序设计带来便利的同时也带来了弊端,比如继承会给程序带来侵入性,程序的可移植性低,增减对象间的耦合性,如果一个类被其他类继承,则当这个类需要修改的时候,就必须考虑到所有的子类,并且父类修改后,所有涉及到的子类都有可能产生故障。
  3. 问题提出?如果正确的使用继承 ===>里氏替换原则。

        1.2定义

        里氏替换原则(Liskov Substitution Principle,LSP)由麻省理工学院计算机科学实验室的里斯科夫(Liskov)女士在 1987 年的“面向对象技术的高峰会议”(OOPSLA)上发表的一篇文章《数据抽象和层次》(Data Abstraction and Hierarchy)里提出来的,她提出:继承必须确保超类所拥有的性质在子类中仍然成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)。

        里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。里氏替换原是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。

        如果对每个类型为T1的对象O1,都有类型T2的对象O2,使得T1定义的所有程序P在所有的对象O1都替代成O2,程序P没有发生任何变化,那么类型T2是类型T1的子类型。换句话说:所有引用基类的地方必须能透明的使用其子类的对象。

        在使用继承的时候,遵守里氏替换原则,在子类中尽量不要重写父类的方法。里氏替换原则告诉我们,继承实际上让两个类的耦合性增强了,在适当的情况下,可以通过聚合、组合、依赖来解决问题。

        1.3里氏替换原则的作用

        里氏替换原则的主要作用如下:

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

        1.4实现方法

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

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

三、应用实例

        下面先看一例不遵守里氏替换原则的案例。

public class Liskov {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        A a = new A();
        System.out.println("11-3=" + a.func1(11, 3));
        System.out.println("1-8=" + a.func1(1, 8));

        System.out.println("-----------------------");
        B b = new B();
        System.out.println("11-3=" + b.func1(11, 3));//这里本意是求出11-3
        System.out.println("1-8=" + b.func1(1, 8));// 1-8
        System.out.println("11+3+9=" + b.func2(11, 3));
    }
}

// A类
class A {
    // 返回两个数的差
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}

// B类继承了A
// 增加了一个新功能:完成两个数相加,然后和9求和
class B extends A {
    //这里,重写了A类的方法, 可能是无意识
    public int func1(int a, int b) {
        return a + b;
    }

    public int func2(int a, int b) {
        return func1(a, b) + 9;
    }
}

        下面就是对上面的这个例子进行改造的遵守里氏替换原则的案例。

public class Liskov {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		A a = new A();
		System.out.println("11-3=" + a.func1(11, 3));
		System.out.println("1-8=" + a.func1(1, 8));

		System.out.println("-----------------------");
		B b = new B();
		//因为B类不再继承A类,因此调用者,不会再func1是求减法
		//调用完成的功能就会很明确
		System.out.println("11+3=" + b.func1(11, 3));//这里本意是求出11+3
		System.out.println("1+8=" + b.func1(1, 8));// 1+8
		System.out.println("11+3+9=" + b.func2(11, 3));
		
		
		//使用组合仍然可以使用到A类相关方法
		System.out.println("11-3=" + b.func3(11, 3));// 这里本意是求出11-3
		

	}

}

//创建一个更加基础的基类
class Base {
	//把更加基础的方法和成员写到Base类
}

// A类
class A extends Base {
	// 返回两个数的差
	public int func1(int num1, int num2) {
		return num1 - num2;
	}
}

// B类继承了A
// 增加了一个新功能:完成两个数相加,然后和9求和
class B extends Base {
	//如果B需要使用A类的方法,使用组合关系
	private A a = new A();
	
	//这里,重写了A类的方法, 可能是无意识
	public int func1(int a, int b) {
		return a + b;
	}

	public int func2(int a, int b) {
		return func1(a, b) + 9;
	}
	
	//我们仍然想使用A的方法
	public int func3(int a, int b) {
		return this.a.func1(a, b);
	}
}

        

猜你喜欢

转载自blog.csdn.net/jokeMqc/article/details/121249121