里氏替换原则

转载请注明出处!!!http://blog.csdn.net/zhonghuan1992

         所有配套代码均在github上:https://github.com/ZHONGHuanGit/DesignPattern


跟着ZHONGHuan学习设计模式

里氏替换原则




         这节中我们会聊聊里氏替换原则,聊它之前,我们先看看定义。

         定义:如果对每一个类型为T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。(摘自java与模式一书)

         如果你觉得定义说的模糊了点,不太清楚,没关系,我们慢慢说明白。里氏替换原则的另一个简短的定义是“所有引用基类的地方必须能透明地使用其子类的对象”。这个可能更清楚点。如果你熟悉的掌握一门面向对象的语言,你应该都可以明白面向对象的继承,子类继承自父类的话,自然的就会继承父类的所有方法(当然前提是父类不要把方法声明为private)。    

在面向对象中,继承有很多优点:

       1代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;

       2提高代码的重用性;

       3子类可以形似父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有父的“种”,“世界上没有两片完全相同的叶子”是指明子与父的不同;

       4提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口都是通过继承父类来完成的;

       5提高产品或项目的开放性。

      

       而里氏替换原则希望,你在写一个类继承自原有的类的同时,尽量不要去更改原有的方法。话说那么多,最好的方式就是举个例子了。

       假设你要做一个枪战游戏,我们省去一些其它的细节,我们关注下,人物在开枪的时候的动作,大家应该玩过CF或CS,或者至少听过枪击类游戏。每个人物一般都有一把枪,所以,这里,我们让选手自己持有枪,也就是让枪称为选手的一个属性。请看下面的代码:

 

例子代码:

/*
 * 虚拟的抢,后面的枪不管如何实现的,都必须保证,shoot这个接口,实现的就是枪的射击,不同的枪的射击方式是不一样的,但是枪的射击理论上有一个共同的步骤
 * 就是子弹出来,射向对方,具体如何我们不管,总之,子类在实现枪这个shoot这个射击方法的接口的时候,不能变成是装子弹,这个就违反了里氏替换原则了
 * 这里是关键,就是射击的方法还是射击,虽然过程细节不一样,但还是射击
 * 如果不明白为什么违反,那么请你再仔细思考下,还是不懂,留言吧。
 *
 */
interface Gun{
       public void shoot();
}
 
//手枪实现Gun接口
class HandGun implements Gun{
       public void shoot()
       {
              System.out.println("手枪射击");
       }
}
 
//AK47实现Gun接口
class AK47 implements Gun{
       public void shoot()
       {
              System.out.println("AK47射击");
       }
}
 
//机枪实现Gun接口
class MachineGun implements Gun{
       public void shoot()
       {
              System.out.println("机枪在装子弹");//这里就是违反了里氏替换原则原则
       }
}
 
class Hero{
       String name;
       Gun gun;
       public Hero(String name)
       {
              this.name=name;
       }
      
       public void setGun(Gun gun)
       {
              this.gun=gun;
       }
      
       public Gun getGun(Gun gun)
       {
              return gun;
       }
      
       public void shoot()
       {
              if(gun==null)
              {
                     System.out.println("没抢啦,快去拿把枪吧");
                     return ;
              }
              gun.shoot();
       }
}
 
 
public class Main{
       public static void main(String[] args)
       {
              Hero hero=new Hero("神枪手");
             
              //给枪手配上AK47
              hero.setGun(new AK47());
              hero.shoot();
             
              //给枪手配上机枪
              hero.setGun(new MachineGun());
              hero.shoot();//本来想设计的,结果却变成了装子弹
                                   //这个还是属于比较明显的问题
             
              hero.setGun(new HandGun());
              hero.shoot();
       }
}




       上面的例子还是比较明显地违反了里氏替换原则,现在我们来看一下,感觉上可以这样设计,看上去没有问题,但是在某种情况下,会遇到问题的。

       来看一看现实与程序设计的矛盾。

       现实中,正方形是特殊的矩形,这个接触过数学的都不陌生了吧。所以,在设计中我们把正方形当做是矩形的子类,似乎是合理的(我们暂且这样,请继续看下求)

 

         这样的设计会出什么问题呢?

       让我们看代码示例吧!

//普通的矩形
class Rectangle
{
	int height, width;

	public int getWidth()
	{
		return width;
	}

	public void setWidth(int width)
	{
		this.width = width;
	}

	public int getHight()
	{
		return height;
	}

	public void setHeight(int height)
	{
		this.height = height;
	}
}

/*
 * 因为Square的长宽相同,用一个size来统一 所以改写了父类的方法,因为它们实际上都是对size进行修改
 */
class Square extends Rectangle
{
	int size;

	public int getWidth()
	{
		return size;
	}

	public void setWidth(int width)
	{
		this.size = width;
	}

	public int getHight()
	{
		return size;
	}

	public void setHeight(int height)
	{
		this.size = height;
	}

	public int getSize()
	{
		return size;
	}

	public void setSize(int size)
	{
		this.size = size;
	}
}

public class Main
{

	/**
	 * 这个方法主要作用就是调整长方形的长宽,让长方形的width调整至比height更长。
	 * 可是就是这样的方法,出问题了,因为如果穿进去的rec实际上是一个正方形的话,那么,程序会无限制的运行下去
	 * 
	 * @param rec
	 * @return
	 */
	static void testRec(Rectangle rec)
	{
		while (rec.getWidth() <= rec.getHight())//
		{
			rec.setWidth(rec.getWidth()+1);
		}
	}

	public static void main(String[] args)
	{
		Rectangle rec = new Square();
		rec.setWidth(5);
		rec.setHeight(6);
		testRec(rec);// 就是这里出现问题了,这个方法会无限的运行下去,不信的话,你试试就ok了

	}
}



       为什么会出现这样的情况,就是因为子类Square在修改父类的setLength等方法时,看上去是没有问题,但是存在的潜在的问题就是面对testRec这样的方法,就会出错,这就是我们需要遵守里氏替换原则的原因之一,不能修改父类。但是不修改父类,我们怎么应对正方形和矩形之间的区别呢?解决方法之一就是抽象一个类(我们在这里称为四边形),正方形和矩形都继承自它,这样就可以解决问题了。

      

       之所以说问题解决了,是因为在运行的时候不会再出现刚刚那样的错误,这样的错误在编译的时候就被解决了。

 

       实例代码请参考github网址https://github.com/ZHONGHuanGit/DesignPattern

      

 

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值