面向对象的原则、模式、语言及框架(五)

[b]Liskov替换原则[/b]
我们前面说了开闭原则OCP,其背后的主要机制是抽象和多态,但在静态语言中(如c++,java),支持抽象和多态的关键机制之一便是继承,正是有了继承,我们才能够
抽象出接口/基类,然后在子类中实现继承而来的抽象方法,或覆写基类已实现的方法
来定制子类。这样我们才能只通过扩展来实现新增的功能。
但是按照什么规则,我们才能设计出最佳的继承层次呢,以及什么样才是最佳的继承体
系呢?Liskov替换原则回答了这个问题。
下面我们就看看什么是Liskov替换原则(LSP):
LSP:子类型必须能够替换成它们的基类型。
LSP的重要性是不言而喻的,例如:
[code]
void f(BaseType bt){
//使用bt来做事情
}
[/code]
这时候如果我们传递一个子类型SubType的对象st:f(st);
如果这时候f出现一个错误的行为,那么SubType就违反了LSP,同时也导致了对
OCP的违反,因为我们要想这个方法对SubType也产生正确的行为,我们需要重新
修改f,对特定SupType进行定制操作,以便得到正确的行为,这样f对BaseType
的子类就不封闭了
我们看看Bob大叔举的一个违反LSP的例子:
[code]
public class Rectangle{
protected int width;
protected int hight;

public void setWidth(int width){
this.width = width;
}
public void setHight(int hight){
this.hight = hight;
}
public int getWidth(){
return width;
}
public int getHight(){
return hight;
}
}
[/code]
我们经常说继承是"Is-a",而组合是"has-a".这样如果一个的对象对于另一个类的对象
满足"is-a"关系,那么就应该把这个新类从原来那个类继承而来。
正方形是个矩形,因此把Square类视为Rectangle类的子类应该是合理的。"Is"被认为是
面向对象设计(OOA)的基本技术之一。但这会产生微妙但极为应该重视的问题。
但我们首先看到width,height两个变量对于Square来说是一种浪费,一般情况下,这种浪费是无关紧要的。但是setHight和setWidth对于Square来说是不合适的,因为正方形的长和宽应该相同的,我们为了确保这点,我们可以复写这两个方法:
[code]
public class Square extends Rectangle{
//others
public void setWidth(int width){
super.setWidth(width);
super.setHight(width);
}
public void setHight(int hight){
super.setHight(hight);
super.setWidth(hight);
}
}
[/code]
这样似乎就满足了数学意义上的正方形了吧,但是我们考虑下面的函数
[code]
void testArea(Rectangle r){
r.setWidth(2);
r.setHeight(3);
assert(r.area()==6);
}
[/code]
当我们向testArea传递Square对象时,断言就会失败,因为testArea的编写者
不会认为高度的改变,会影响宽度。方法testArea表明Rectangle和Square的结构
是脆弱的,Square不能够替换掉Rectangle,因此Square和Rectagle之间的关系是违反了
LSP。
LSP让我们得出了一个非常重要的结论:一个模型,如果孤立的看,并不能发现问题,模型
的有效性只能通过它的客户程序来表现。如果孤立的看,最后那个版本的模型时自相容的,
但是如果从基类做出一些合理假设的程序员的角度来看,这个模型是有问题的。

但是有谁能知道使用者会做出怎样的假设呢?大多数的假设是很难预测的。事实上如果我们
试图去预测所有这种假设,我们所得到的系统将充满不必要复杂性的Bad Smell.所以通常最好的方法是之预测那些最明显的对于LSP违反情况而推迟其他的预测。
真正原因:
[b]IS-A是关于行为的[/b]
Square和Rectangle这个显然合理的模型为什么会出现问题?毕竟Square就是Rectangle,
难道它们之间不存在is-a关系么?
对于不是testArea的编写者来说Square就是Rectangle是没有问题的,但对于testArea得编写者而言,Square对象绝对不是Square,应为Square的行为方式和testArea所期望的行为方式是不相容的。对象的行为方式才是软件真正关注的问题,LSP清楚地告诉我们,OOD中
is-a是对于行为方式而言的,行为方式的合理假设是客户程序所依赖的。
另外一个问题:
我们都知道Java的异常,子类不能比超类抛出更多的异常,这其实就是LSP原则。
结论:
OCP是OOD的核心原则,如果这个原则应用的有效,应用程序就会有更好的维护性、可重用性和健壮性,而LSP是OCP成为可能的主要原则之一,正是子类型的可替换性,才使得使用基类型的模块无需修改的情况下就能进行扩展。这种可替换性是开发人员可以隐式依赖的东西。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值