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

一、概念:

里氏替换原则:LSP (Liskov Substitution Principle),如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型。

通俗的定义:所有引用基类的地方必须能透明地使用其子类的对象。

二、例子:

以浇水为例。人,拿到工具【水管、水桶、瓶子】,装水后都可以浇水。【水管、桶、瓶子】都可以获取水。应该有个loadWater方法。有watering 浇水功能。人浇水,人只关注浇水。拿到工具就浇水,不用考虑浇水的细节。流程是,人拿工具,用拿到的工具浇水。

类图如下:

代码如下:

Tools 抽象类:

package dim.LSP.simples;

public abstract class Tools {

	/**
	 * 装水
	 */
	public void loadWater() {
	}
	/**
	 * 浇水
	 */
	public void watering() {
	}
}

Bottle瓶子也可以是浇水工具,继承工具类Tools

package dim.LSP.simples;

public class Bottle extends Tools{

	@Override
	public void loadWater() {
		// TODO Auto-generated method stub
		System.out.println("Bottle load water");
	}

	@Override
	public void watering() {
		// TODO Auto-generated method stub
		System.out.println("bottle watering");
	}

}

waterPipe类:

package dim.LSP.simples;

public class WaterPipe  extends Tools{

	@Override
	public void loadWater() {
		// TODO Auto-generated method stub
		System.out.println("pipe load water");
	}

	@Override
	public void watering() {
		// TODO Auto-generated method stub
		System.out.println("pipe watering");
	}

}

Bucket类:

package dim.LSP.simples;

public class Bucket extends Tools{

	@Override
	public void loadWater() {
		// TODO Auto-generated method stub
		System.out.println("bucket load water");
	}

	@Override
	public void watering() {
		// TODO Auto-generated method stub
		System.out.println("bucket watering");
	}

}

种植户,浇水的人:

package dim.LSP.simples;

public class Planter {

	Tools tool=null;
	public Planter() {
		// TODO Auto-generated constructor stub

	}
	public void setTool(Tools tool)
	{
		this.tool=tool;
	}
	public void waterPlant()
	{
		tool.loadWater();
		tool.watering();
	}

}

测试类:

package dim.LSP.simples;

public class TestClass {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

	         <span style="color:#3333ff;"><strong>	Planter planter=new Planter();
		//用瓶子浇水
		planter.setTool(new Bottle());
		planter.waterPlant();

		//用水管浇水
		planter.setTool(new WaterPipe());
		planter.waterPlant();

	</strong></span>

	}

}

运行结果如下:用瓶子装水,浇水。用水管装水,浇水。

Bottle load water

bottle watering

pipe load water

pipe watering

看测试类代码,浇水的人,拿到工具就浇水。planter 里面:

	public void setTool(Tools tool)
	{
		this.tool=tool;
	}
	public void waterPlant()
	{
		tool.loadWater();
		tool.watering();
	}

测试类里的代码,只要拿了工具,就可以浇水。不用考虑浇水的细节:

	Planter planter=new Planter();
		//用瓶子浇水
		planter.setTool(new Bottle());
		planter.waterPlant();

		//用水管浇水
		planter.setTool(new WaterPipe());
		planter.waterPlant();

实现子类对象用父类对象替换。父类能出现的地方,子类就可以出现。也就是概念中的,引用基类的地方必须能透明地使用子类对象。

但是这里有个问题,水管,怎么还要装水。水管直接可以浇水。怎么处理比较合适?可以把水管独立出来,独立为直接浇水的工具,做个单独的抽象类。

类图如下:

package dim.LSP.simples;

public abstract class DirectTools {

}

DirectTools类,可扩展:

package dim.LSP.simples;

public abstract class DirectTools {

}

DirectWaterPipe 代码:

package dim.LSP.simples;

public class DirectWaterPipe extends DirectTools {

	Tools tool=new Tools() {

		@Override
		public void watering() {
			// TODO Auto-generated method stub
			System.out.println("watering directly");
		}

		@Override
		public void loadWater() {
			// TODO Auto-generated method stub

		}
	};
    public Tools  getTools()
    {

    	return tool;
    }
}

测试类:

package dim.LSP.simples;

public class TestClass {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Planter planter=new Planter();
		//用瓶子浇水
		planter.setTool(new Bottle());
		planter.waterPlant();

		//用水管浇水
		planter.setTool(new WaterPipe());
		planter.waterPlant();

<span style="color:#3333ff;"><strong>		//用水管直接浇水
		planter.setTool(new DirectWaterPipe().getTools());
		planter.waterPlant();</strong></span>

	}

}

运行结果:

Bottle load water

bottle watering

pipe load water

pipe watering

watering directly

也可以把DirectWaterPipe 类直接继承Tools,重写loadWater 方法,里面什么也不做。这样有点变扭。

例2:

以视图View为例。View 可以是Button,TextView等等。View 有获取ID,设置ID,监听click等方法。把Button 的对象传给父类View 的对象。

类图如下:

代码如下:

View 抽象类:

package dim.LSP.simples.view;

public   abstract  class View {

	/**
	 * set the id of view
	 * @return
	 */
	public int getId() {
		return 0;
	}
	/**
	 * get the id of view
	 * @param id
	 */
	public   void setId(int id) {
	}
	/**
	 * listener
	 */
	public void onClickListener() {
	}
}

Button类,继承View类:

package dim.LSP.simples.view;

public class Button extends  View{

	int btnId=0;
	@Override
	public int getId() {
		// TODO Auto-generated method stub
		return btnId;
	}

	@Override
	public void setId(int id) {
		// TODO Auto-generated method stub
		this.btnId=id;
	}

	@Override
	public void onClickListener() {
		// TODO Auto-generated method stub
		System.out.println("click button now");
	}

}

TextView类:

package dim.LSP.simples.view;

public class TextView extends View{

	private int textVid=0;
	@Override
	public int getId() {
		// TODO Auto-generated method stub
		return textVid;
	}

	@Override
	public void setId(int id) {
		// TODO Auto-generated method stub
		this.textVid=id;
	}

	@Override
	public void onClickListener() {
		// TODO Auto-generated method stub
		System.out.println("click textView now ");
	}

}

Activity类:

package dim.LSP.simples.view;

public class Activity {

	public int  getId(View v)
	{
		return v.getId();
	}

	public void click(View v)
	{
		System.out.println("view Id is "+v.getId());
		v.onClickListener();
	}
}

测试类:

package dim.LSP.simples.view;

public class TestClass {

public static void main(String[] args) {
	Activity activity=new Activity();

	//设置button ID,按一下,button
	View  btn=new Button();
	btn.setId(111);
	 activity.click(btn);

	 //设置TextView id ,按一下TextView
	View textView=new TextView();
	textView.setId(888);
	activity.click(textView);
}
}

测试结果:

view Id is 111

click button now

view Id is 888

click textView now 

上面的类都做了简单的抽象,如果不用抽象类会如何?

类图如下:

使用者,每次用新工具时,都要,调用loadWater 和watering 。每次用新工具都要修改Planter类。不知道会不会抓狂。抽象了之后,可以屏蔽很多细节。

三、4层含义:

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

  • 子类可以实现父类的抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

这里可能会有疑问,为什么不把View和Tools设为接口。感兴趣可以看看这篇文章:接口与抽象类的区别

有所不足、多多指正、共同进步!

相关链接:设计模式六大原则之单一职责原则

参考资料:

接口与抽象类的区别

《设计模式之禅》

《HeadFirst》

《StartUML详解》

设计模式六大原则

设计模式之六大原则

时间: 2024-12-28 21:38:32

设计模式六大原则之里氏替换原则的相关文章

学习设计模式 - 六大基本原则之里氏替换原则

设计模式总共有六大基本原则,统称为SOLID (稳定)原则,分别是S-单一职责原则(Single Responsibility Principle), O-开闭原则(Open closed Principle),L-里氏替换原则(Liskov Substitution Principle),L-迪米特法则(Law of Demeter),I-接口隔离原则(Interface Segregation Principle),D-依赖倒置原则(Dependence Invension Principl

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

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

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

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

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

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

六大原则之里氏替换原则

阐述一下: 肯定有不少人跟我刚看到这项原则的时候一样,对这个原则的名字充满疑惑.其实原因就是这项原则最早是在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:所有引用基类的地方必须能透明地使用其子类的对象. 如

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

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

设计原则之里氏替换原则

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

深入理解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