里氏代换原则 举例 分析

里氏代换原则
定义:里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。
目的:里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
价值:里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
说实话,“任何基类可以出现的地方,子类一定可以出现”,这话太抽象了,我根本找不出另例子来证明,什么是符合这个原则的,什么是不符合这个原则的。
子类一定可以出现,从语言的基本要求角度来讲,这是必须的,面向对象中父类就是可以被子类替换的,那么这个要求针对的是啥?
是需求的层面,因为子类替换父类,在技术上一定是允许的,那么我们在设计上要加些什么样的限制来保证这一点呢。
一个一个的举例吧:
1.类作为函数的参数:函数(类)
这种情况,那对该函数的设计要求是:该函数不能处理个性的需求,也就是说,不应该去处理基类中某一类型所特有的机能,否则这个函数设计不合格。
对类的设计要求是:个性的东西一定要自己处理,不能公开。
2.类做未一个调用函数的对象:类.函数()
这种情况,对该函数的设计要求是,该函数必须处理的是共性的内容,个性的内容要用通过this指针去处理。

通过上面的分析,视乎有点眉目,但仍然感觉不太具体。
一.如下是一个类的设计

// 长方形
class Rectangle{
private:
	int h;
	int w;
public:
	void setH(int value){
		h = value; 
	}
	void setW(int vaule){
		w = vaule;
	}
	int getH(){
		return h;
	}
	int getW(){
		return w;
	}
};
// 正方形
class Square:public Rectangle{
	void set(int value){
		setH(value);
		setW(value);
	}
};

二.到这来应该是没有问题的,因为LSP是一个需求级别的设计要求。
现在假设有这么一个要求,保证长方形的宽度一定要大于高度。
函数如下:

class Clinet{
public:
	void setRectangle(Rectangle* r){
		if(r->getW()<r->getH()){
			r->setW(r->getH()+1);
		}
	} 
};

显然这里:设计部符合LSP的设计原则了。
那么问题出在哪里?就是Clinet的setRectangle的入参是正方形就出问题了。
怎么办呢?
三.修改设计,其实修改的方法有很多,只要能把问题解决掉就行。
方案1:对正 方形进行特殊处理

// 长方形
class Rectangle{
private:
	int h;
	int w;
public:
	void setH(int value){
		h = value; 
	}
	void setW(int vaule){
		w = vaule;
	}
	int getH(){
		return h;
	}
	int getW(){
		return w;
	}
	//设计变更
	virtual void setRectangle(){
		if(getW()<getH()){
			setW(getH()+1);
		}
	} 
};
// 正方形
class Square:public Rectangle{
	void set(int value){
		setH(value);
		setW(value);
	}
	//设计变更
	virtual void setRectangle(){
		
	}
};
class Clinet{
public:
	void setRectangle(Rectangle* r){
		//设计变更
		r->setRectangle();
	} 	
};

在好多人眼里,这种修改方案可能不是一个优雅的方案,但很实用,下面在看看更优雅一点的方案。
方案2:提取更高级别的抽象
// 四边形

class Quadrilateral{
protected:
	int h;
	int w;
public:
	virtual void setH(int value)=0;
	virtual void setW(int vaule)=0;
	virtual int getH()=0;
	virtual int getW()=0;
};
// 长方形
class Rectangle: public Quadrilateral{
public:
	virtual void setH(int value){
		h = value; 
	}
	virtual void setW(int vaule){
		w = vaule;
	}
	virtual int getH(){
		return h;
	}
	virtual int getW(){
		return w;
	}
};
// 正方形
class Square:public Quadrilateral{
private:
	void setSide(int value){
		h = w = value; 	
	}
public:
	virtual void setH(int value){
		setSide(value); 
	}
	virtual void setW(int vaule){
		setSide(vaule);
	}
	virtual int getH(){
		return h;
	}
	virtual int getW(){
		return w;
	}
};
class Clinet{
public:
	void setRectangle(Rectangle* r){
		if(r->getW()<r->getH()){
			r->setW(r->getH()+1);
		}
	} 	
};

//客户端调用
int main()
{
	cout<<"建造者模式演示\n";
	

	//看代码不用考虑以下内容
	int cin_a;
	cin>>cin_a;
	return 0;
}

总结
设计无非就是解决共性和个性的问题,LSP做为一个原则,其实我们即使不学这个原则,也不一定会遵守这个原则,因为如果你真的做了一个不符合这个原则的设计,那么运行马上就会遇到问题。
解决好共性和个性,就一定是一好的设计,一个符合各种设计原则的设计。
对于LSP这个原则,我其实一直都是理解的云里雾里的,直到现在才把这个东西具体化,所以,我觉得什么抽象的东西都试着举例说明,要不然你永远不会有一个清晰的思路。

你可能感兴趣的:(设计模式)