6大设计原则(2):里氏替换原则

里氏替换原则:LSP

定义:

如果对于每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都换为o2时,程序的行为没有发生变化,那么S是T的子类型。

在继承的时候,父类出现的地方子类就可以出现,子类可替代父类,因为子类中有父类的方法,然而父类却不可以替代子类,因为子类中可能有父类没有的方法。这就是所谓的向下转型是不安全的。

使用继承有很多优点,可以提高代码的重用性,提高可扩展性、开放性,但是不可否认,继承也是有缺点的:

1.继承是侵入性的,只要继承,就必须拥有父类的所有属性和方法;

2.降低代码的灵活性

3.增强了耦合性。

解决方案就是里氏替换原则。

4个含义:

1.子类必须完全实现父类的方法

2.子类可以有自己的方法

3.覆盖或实现父类的方法时,输入参数可以被放大

4.覆写或实现父类的方法时,输出结果可以被缩小

前两个含义比较好理解,这里就不再赘述,主要说一下3和4。

先说第3个,覆盖或实现父类的方法时,输入参数可以被放大。

先看一个例子:

class Father {
	public Collection dosomething(HashMap map) {
		System.out.println("父类被执行--->");
		return map.values();
	}
}

class Son extends Father {
	public Collection dosomething(Map map) {
		System.out.println("子类被执行--->");
		return map.values();
	}
}

public class Client {
	public static void main(String[] args) {
		// 父类存在的地方就可以替换为子类
		Father f = new Father();
		HashMap map = new HashMap();
		f.dosomething(map);
	}
}

代码运行结果是:

父类被执行--->

根据里氏替换原则,将父类改为子类:

public class Client {
	public static void main(String[] args) {
		// 父类存在的地方就可以替换为子类
		// Father f = new Father();
		Son f = new Son();
		HashMap map = new HashMap();
		f.dosomething(map);
	}
}

然而输出结果还是父类被执行。。。

父类方法的参数是HashMap,而子类方法的参数是Map,也就是说子类的参数类型范围大,子类代替父类传递到调用者中,子类的方法永远不会被执行。如果想要执行子类中的方法的话就需要覆写父类中的方法,覆写就是父类中的方法一模一样的出现在子类中。

class Father {
	public Collection dosomething(HashMap map) {
		System.out.println("父类被执行--->");
		return map.values();
	}
}

class Son extends Father {
	// public void dosomething(Map map) {
	// System.out.println("子类被执行--->");
	// }
	@Override
	public Collection dosomething(HashMap map) {
		// TODO Auto-generated method stub
		System.out.println("子类被执行--->");
		return map.values();
	}
}

public class Client {
	public static void main(String[] args) {
		// 父类存在的地方就可以替换为子类
		// Father f = new Father();
		Son f = new Son();
		HashMap map = new HashMap();
		f.dosomething(map);
	}
}

这是正常的。

如果父类参数的参数类型范围大于子类输入参数类型的话,会出现什么问题呢?会出现父类存在的地方,子类就未必可以存在,因为一旦把子类作为参数传入,调用者就很可能进入子类的方法范畴。

修改一下上面的代码,扩大父类参数范围,缩小子类参数范围。

class Father {
	public Collection dosomething(Map map) {
		System.out.println("父类被执行--->");
		return map.values();
	}
}

class Son extends Father {
	public Collection dosomething(HashMap map) {
		System.out.println("子类被执行--->");
		return map.values();
	}

}

public class Client {
	public static void main(String[] args) {
		// 父类存在的地方就可以替换为子类
		Father f = new Father();
		Son f1 = new Son();
		HashMap map = new HashMap();
		f.dosomething(map);
		f1.dosomething(map);
	}
}

f执行父类的方法,f1执行子类的方法。

这就不正常了

子类在没有覆写父类方法的情况下,子类方法被执行了。所以,子类中方法的参数范围(前置条件)必须与父类的参数范围(前置条件)相同或者更加宽松。

再来说一下第4个含义,覆写或实现父类的方法时,输出结果可以被缩小。

什么意思呢?父类方法的返回值是类型T,子类相同方法(重载或覆写)的返回值是S,那么里氏替换原则就要求S必须小于等于T。也就是说,要么S和T类型相同,要么S是T的子类。

对于覆写而言,父类和子类中的方法时一模一样的,所以返回类型也应当是一样的。

对于重载,也就是第3个含义所讲到的,子类的输入参数宽于或等于父类的参数,也就是说这个方法时不会被调用的。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-06 02:39:51

6大设计原则(2):里氏替换原则的相关文章

面向对象原则之一 里氏替换原则

原文:面向对象原则之一 里氏替换原则 前言 面向对象有人分为五大原则,分别为单一职责原则.开放封闭原则.依赖倒置原则.接口隔离原则.里氏替换原则. 也有人分为六大原则,分别为单一职责原则.开放封闭原则.依赖倒置原则.接口隔离原则.里氏替换原则.迪米特法则. 现在我们来介绍里氏替换原则 里氏替换原则 1)概念 其概念是子类对象能够替换其基类对象被使用. 听上面的概念好像很简单,不就是父类实现的方法就能被子类实现,父类在外部的调用,替换成子类也可以嘛. 这么理解就错了,这里的概念虽然说得简单,但是其

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

前言 上一节中我们介绍了,依赖倒置,依赖倒置利用抽象的稳定性来架构我们的系统,是我们经常能遇到的一种原则,比如说面向接口编程. 这一节中,我们来说说里氏替换原则,这个原则其实非常非常的简单,其实与依赖倒置相结合来看,就是希望我们用抽象的方法来构建项目而非具体的实现,里氏替换原则就是推荐我们不要重写父类中具体的实现来构建我们的项目. 我们来深入研究研究. 基本介绍 继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对

设计原则之里氏替换原则

定义:所有引用基类的地方必须能透明地使用其子类的对象. 问题:有一功能P1,由类A来完成.现在需要将功能P1进行扩展,扩展后的功能为P(P由原有功能P1和新功能P2组成). 功能P由类A的子类B来完成,子类B在完成新功能P2的同时有可能会导致原有功能P1发生故障. 解决:当使用继承时,遵循里氏替换原则.类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法, 也尽量不要重载父类A的方法. 举个栗子:士兵使用武器进行射击,包括武器的类别和特点的介绍. 情况一:士兵使用手枪进行射

day01_面向对象五大原则_1.单一职责原则&2.里氏替换原则

单一职责原则:Single Responsibility Principle (SRP) 一个类,只有一个引起它变化的原因.应该只有一个职责.每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起.这会导致脆弱的设计.当一个职责发生变化时,可能会影响其它的职责.另外,多个职责耦合在一起,会影响复用性.例如:要实现逻辑和界面的分离. T负责两个不同的职责:职责P1,职责P2.当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障.也就是

六大原则之里氏替换原则

阐述一下: 肯定有不少人跟我刚看到这项原则的时候一样,对这个原则的名字充满疑惑.其实原因就是这项原则最早是在1988年,由麻省理工学院的一位姓里的女士(Barbara Liskov)提出来的. 定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型. 定义2:所有引用基类的地方必须能透明地使用其子类的对象. 问题由来:有一功能P1,由类

设计模式原则之里氏替换原则

里氏替换原则,OCP作为OO的高层原则,主张使用“抽象(Abstraction)”和“多态(Polymorphism)”将设计中的静态结构改为动态结构,维持设计的封闭性.“抽象”是语言提供的功能.“多态”由继承语义实现. 定义1:如果对每一个类型为T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型. 定义2:所有引用基类的地方必须能透明地使用其子类的对象. 如

深入理解JavaScript系列(8):S.O.L.I.D五大原则之里氏替换原则LSP

前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第3篇,里氏替换原则LSP(The Liskov Substitution Principle ). 英文原文:http://freshbrewedcode.com/derekgreer/2011/12/31/solid-javascript-the-liskov-substitution-principle/ 开闭原则的描述是: Subtypes must be substitutable for their ba

设计模式六大原则之里氏替换原则

一.概念: 里氏替换原则:LSP (Liskov Substitution Principle),如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型. 通俗的定义:所有引用基类的地方必须能透明地使用其子类的对象. 二.例子: 以浇水为例.人,拿到工具[水管.水桶.瓶子],装水后都可以浇水.[水管.桶.瓶子]都可以获取水.应该有个loadWater方法.有watering 浇水功能

设计模式六大原则:里氏替换原则

里氏替换原则: 子类应当可以替换父类并出现在父类能够出现的地方.比如:公司搞年度派对,都有员工都可以抽奖,那么不管是新员工还是老员工,也不管是总部员工还是外派员工,都应当可以参加抽奖. 里氏替换至少包含一下两个含义: 1.里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义.子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑

七大原则四-->里氏替换原则

里氏替换原则 解决类继承(对象)代码耦合性问题 继承关系中 父类修改 会影响子类 基本介绍1) 里氏替换原则(Liskov Substitution Principle)在1988年,由麻省理工学院的以为姓里的女士提出的.2) 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型.换句话说,所有引用基类的地方必须能透明地使用其子类的对象.3) 在使用继承时,遵循里氏替换原则,在