`
wangym
  • 浏览: 123354 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

六大设计原则之“里氏替换原则”

阅读更多

通俗地讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必应能适应。

 

里氏替换原则为良好的继承定义了一个规范,一句简单的定义包括了四层含义:

 

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

 

父类:AbstractGun

public abstract class AbstractGun {

	abstract void shoot();
}

 

子类之步枪:Rifle

public class Rifle extends AbstractGun {

	@Override
	void shoot() {
		System.out.println("步枪射击...");
	}
}

 

子类之机枪:MachineGun

public class MachineGun extends AbstractGun {

	@Override
	void shoot() {
		System.out.println("机枪射击...");
	}
}

 

士兵类:Soldier

public class Soldier {

	private AbstractGun gun;

	// 务必要用父类或接口
	public void setGun(AbstractGun _gun) {
		this.gun = _gun;
	}

	public void kill() {
		System.out.println("士兵开始射击...");
		this.gun.shoot();
	}
}

 

场景类:

public class Run {

	public static void main(String[] args) {
		Soldier sanmao = new Soldier(); // 创建新兵三毛
		sanmao.setGun(new Rifle()); // 给予一把步枪
		sanmao.kill(); // 三毛开始杀敌
	}
}

注意:在类中调用其他类时务必要用父类或接口,若不能使用,则说明类的设计已经违背原则。

 

常见畸型继承举例:

 

接上面的应用举例,如果我们要把一把玩具枪给三毛,那么新增类:ToyGun

public class ToyGun extends AbstractGun {

	// 玩具枪是不能射击的,但是为了塞给三毛,必须要继承父类
	// 继承后又要求实现方法,怎么办?我们一般就强制继续一个
	@Override
	void shoot() {
		// 玩具枪的性质是无法射击的,所以无法真正实现该方法
	}
}

通常应用中经常发生如上情况,按LSP原则,则要注意:若子类不能完整地实现父类的方法,或者父类的某些方法在子类中发生畸变,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。

 

所以如上情况,建议方案是:ToyGun脱离继承,建立一个独立的父类(如AbstractToy),为了实现代码复用,可以与AbstractGun建立关联委托关系。例如,可以在AbstractToy中声明将声音、形状都委托给AbstractGun处理,玩具枪嘛,形状和声音都要和真枪一样,然后两个基类下的子类自由延民

 

2、子类应避免自己的个性

 

子类是可以有自己的方法和属性,但在LSP原则里,可以正着用不能反过来用。在子类出现的地方,父类未必就可以胜任。

 

比如,现在又有一把新枪AUG,它是狙击枪也是步枪的一种:

public class AUG extends Rifle {

	public void zoomOut() {
		System.out.println("通过望远镜观察...");
	}

	public void shoot() {
		System.out.println("AUG射击");
	}
}

这时在调用AbstractGun的各子类时,只有AUG有自己的个性方法即zoomOut,而其它的子类却都没有。那么要真正使用zoomOut方法,则必须直接调用AUG子类,不能使用抽像类或接口,则违背了上述第一个标红注意点。比如,要让Soldier类使用AUG的个性方法zoomOut,则不能使用:AbstractGun _gun为_gun属性的类型,则需直接调用AUG:AUG _gun。

 

3.覆盖或实现父类的方法时输入参数应相同或更宽松

 

举例,父类:

public class Father {

	public Collection doSomething(HashMap map) {
		System.out.println("father...");
		return map.values();
	}
}

 

子类:

public class Son extends Father {

	public Collection doSomething(Map map) {
		System.out.println("son...");
		return map.values();
	}
}

 

场景类:

	public static void main(String[] args) {
		Son obj = new Son(); // 此处若改成 new Father结果也一样
		HashMap map = new HashMap();
		obj.doSomething(map);
	}

运行结果:father...

 

此处是重载Overload,而不是覆写Override。

 

子类的输入参数类型的范围扩大了,将父类中的HashMap扩大到Map,子类代替父类传递到调用者中,子类永远不会被执行,这是正确的。如果你想让子类执行,必须覆写父类的方法。但若反过来,子类参数类型的范围小于父类,那么父类存在的地方,子类就未必可以存在 。则子类就会被执行,这会影发业务逻辑混乱,因为在实际应用中父类一般是抽像类,子类是实现类,这样的一个子类会歪曲父类的意图。

 

4.覆 或实现父类方法时输出结果的类型应小于等于父类的结果类型

 

比如,父类的一个方法返回值是类型T,子类的相同方法(重载或覆盖)的返回值是S,那么里氏替换原则就要求S必须小于等于T,也就是说,要么S和T是同一个类型,要么S是T的子类。

 

输入参数:大于等于

输出参数:小于等于

 

总结:采用里氏替换原则的目的就是增强程序的健壮性,版本升级时也可以保持非常好的兼容性。即使增加子类,原有的子类也可以继续运行。在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑。

 

采用本原则时,应该尽量避免子类有“个性”,一旦子类有了个性,与父类之间的关系就难以调和。把子类当做父类用,子类的个性被抹杀;把子类单独作为一个业务来使用,则代码间的耦合关系就过于复杂缺乏类替换的标准。

 

2
0
分享到:
评论

相关推荐

    设计模式之六大原则详解,Markdown笔记

    详细介绍了设计模式六大原则,配有示例代码和图片,有开闭原则,单一职责原则,里氏替换原则,依赖倒置原则,接口隔离原则,迪米特法则等等。

    设计模式六大原则

    设计模式六大原则(1):...设计模式六大原则(2):里氏替换原则 设计模式六大原则(3):依赖倒置原则 设计模式六大原则(4):接口隔离原则 设计模式六大原则(5):迪米特法则 设计模式六大原则(6):开闭原则

    php 设计模式六大原则

    php 设计模式六大原则 单一职责原则 里氏替换原则 依赖倒置原则 接口隔离原则 迪米特法则 开闭原则 word版

    设计模式六大原则.doc

    设计模式六大原则(1):...设计模式六大原则(2):里氏替换原则 设计模式六大原则(3):依赖倒置原则 设计模式六大原则(4):接口隔离原则 设计模式六大原则(5):迪米特法则 设计模式六大原则(6):开闭原则

    面向对象六大设计原则

    2、里氏替换原则(Liskov Substitution Principle,LSP) 3、依赖倒置原则(Dependence Inversion Principle,DIP) 4、接口隔离原则(Interface Separate Principle,ISP) 5、合成/聚合复用原则(Composite/...

    JAVA设计模式六大原则详细讲解(面向对象语言通用)

    里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改...

    设计模式六大原则 设计模式详解

    详细介绍设计模式的六大原则,有不足之处希望大家多指教。参考《设计模式之禅》

    24种设计模式与6大设计原则

    策略模式[STRATEGY PATTERN] 代理模式[PROXY PATTERN] 单例模式[SINGLETON PATTERN] 多例模式[MULTITION PATTERN] ...六大设计原则:单一职责原则,里氏替换原则,依赖倒置原则,接口隔离原则,迪米特法则,开闭原则。

    24种设计模式介绍与6大设计原则

    二、设计模式的六大原则 1、开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序...

    JAVA六大原则代码.zip

    里氏替换原则(Liskov Substitution Principle,LSP):子类应该能够替换掉父类并且工作正常,即子类必须能够完全替代父类的功能而不产生错误。这个原则保证了代码的可靠性和稳定性。 接口隔离原则(Interface ...

    设计模式6大原则.doc

    设计模式六大原则:单一职责模式、开闭原则、接口隔离原则、里氏替换原则、依赖倒置原则、迪米特法则

    浅谈C#六大设计原则

    这六种原则分别为单一职责原则、接口隔离原则、里氏替换原则、迪米特法则、依赖倒置原则、开闭原则。 单一职责原则 单一职责原则(SRP:Single responsibility principle),规定一个类中应该只有一个原因引起类的...

    程序设计六大原则及代码样例

    本文档非常详细的介绍了程序设计六大原则的定义,问题由来,解决方案及代码样例。

    Beatles9527#StudyNotes#_1设计模式六大原则1

    1. 单一职责原则 2. 依赖倒置原则 3. 迪米特法则 4. 开放-封闭原则 5. 里氏替换原则(了解) 6. 接口隔离原则(了解)

    设计模式总结

    里氏替换原则(Liskov Substitution Principle,LSP) 只要父类出现的地方都可以用子类替换。 依赖倒置原则(Dependece Inversion Principle,DIP) 面向接口编程。细节应该依赖抽象。 依赖可以传递。 依赖有三...

    24个设计模式与6大设计原则

    26.2 里氏替换原则【LISKOV SUBSTITUTION PRINCIPLE】 297 26.3 依赖倒置原则【DEPENDENCE INVERSION PRINCIPLE】 309 26.4 接口隔离原则【INTERFACE SEGREGATION PRINCIPLE】 310 26.5 迪米特法则【LOW ...

    Android源码设计模式解析与实战

    主要讲解面向对象的六大原则、主流的设计模式以及MVC和MVP模式。主要内容为:优化代码的首步、开闭原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特原则、单例模式、Builder模式、原型模式、工厂方法模式、...

    设计模式整体框架与结构

    原则:设计模式遵循六大原则,包括开闭原则、里氏替换原则、依赖倒置原则等,这些原则指导开发者如何正确地应用设计模式。 常用模式:例如单例模式、工厂模式、观察者模式等,每种模式都有其特定的应用场景和解决的...

Global site tag (gtag.js) - Google Analytics