保持系统灵活性

北大青鸟大学城校区logo 北大青鸟大学城校区
招生简章校园环境师资力量就业明星招生问答软件工程师北京大学学历学员项目联系我们 报名通道

免费在线咨询通道>>

免费在线报名通道>>

北大青鸟报名电话
当前位置:北大青鸟 > 北大青鸟程序人生 >

保持系统灵活性

标签:   分类:北大青鸟程序人生

“深层次的继承是很棒的。如果你需要其他类的函数,直接继承它们就好了!不要担心你创建的新类会造成破坏,你的调用者可以改变他们的代码。这是他们的问题,而不是你的问题。”

保持系统灵活性的关键方式,是当新代码取代原有代码之后。其他已有的代码不会意识到任何差别。例如,某个开发人员可能想为通信的底层架构添加一种新的加密方式,或者使用同样的接口实现更好的搜索算法。只要接口保持不变,开发人员就可以随意修改实现代码,而不影响其他任何现有代码。然而,说起来容易,做起来难。所以需要一点指导来帮助我们正确地实现。因此.去看看Barbara liskov的说法。

Liskov替换原则【Lis88]告诉我们:任何继承后得到的派生类对象,必须可以替换任何被使用的基类对象,而且使用者不必知道任何差异。换句话说,某段代码如果使用了基类中的方法,就必须能够使用派生类的对象,并且自己不必进行任何修改。

这到底意味着什么?假定某个类中有一个简单的方法,用来对一个字符串列表进行排序。然后返回一个新的列表。并用如下的方式进行调用:

utils=new BasicUtils();

....

sortedlist=utils.sort(alist);

现在假定开发人员派生了一个BasicUtils的子类,并写了一个新的sort()方法,使用了更快、更好的排序算法:

utils=new BasicUtils();

...

sortedList=utils.sort(alist);

注意对sort()的调用是完全一样的,一个FasterUtils对象完美地替换了一个BasicUtils对象。调用utils.sort() 的代码可以处理任何类型的utils对象,而且可以正常工作。

但如果开发人员派生了一个BasicUtils的子类。并改变了排序的意义——也许返回的列表以相反的顺序进行排列——那就严重违反了Liskov替换原则。

要遵守liskov替换原则,相对基类的对应方法,派生类服务(方法)应该不要求更多,不承诺曼少:要可以进行自由的替换。在设计类的继承层次时。这是一个非常重要的考虑因素。

继承是oo建模和编程中被滥用最多的概念之一。如果违反了liskov替换原则,继承层次可能仍然可以提供代码的可重用性,但是将会失去可扩展性。类继承关系的使用者现在必须要检查给定对象的类型。以确定如何针对其进行处理。当引入了新的类之后,调用代码必须经常重新评估并修正。这不是敏捷的方式。

但是可以借用一些帮助。编译器可以帮助开发人员强制执行Lisk0V替换原则,至少在某种程度上是可以达到的。例如,针对方法的访问修饰符。在Java中。重写方法的访问修饰符必须与被重写方法的修饰符相同-或者可访闯范围更加宽大。也就是说,如果基类方法是受保护的。那么派生重写方法的修饰符必须是受保护的或者公共的。在C#和VB.NET中,被重写方法与重写方法的访问保护范围必须完全相同。

考虑一个带有findLargest()方法的类Base,方法中抛出一个IndexOutofRangeException异常。基于文档,类的使用者会准备抓住可能被抛出的异常。现在,假定你Base类继承得到类Derived,并重写了findLargest()方法,在新的方法中抛出了一个不同的异常。现在,如果某段代码期待使用Base类对象,并调用了Derived类的实例,这段代码就有可能接收到一个意想不到的异常。你的Derived类就不能替换使用到Base类的地方。在Java中,r通过不允许重写方法抛出任何新的榆查异常避免了这个问题,除非异常本身派生自被重写方法抛出的异常类(当然,对于像RuntimeException这样的未检查异常。缩译器就不能帮你了)。

不幸的是,Java也违背了Liskov替换原则。java.util.stack类派生自java. util.vector类。如果开发人员(不小心)将stack对象发送给一个期待vector实例的方法,Stack中的元素就可能被以与期望的行为不符的顺序被插入或删除。

当使用继承时,要想想派生类是否可以替换基类。如果答案是不能,就要问问自己为什么要使用继承。如果答案是希望在编写新类的时候,还要重用基类中的代码,也许要考虑转而使用聚合。聚合是指在类中包含一个对象,并且该对象是其他类的实例,开发人员将责任委托给所包含的对象来完成(该技术同样被称为委托)。

那么继承和委托分别在什么时候使用呢?

如果新类可以替换已有的类,并且它们之间的关系可以通过is-a来描述,就要使用继承。

如果新类只是使用已有的类,并且两者之间的关系可以描述为has—a或是uses-a,就使用委托吧。

开发人员可能会争辩说,在使用委托时,必须要写很多小方法,来将方法调用指向所包含的对象。在继承中,不需要这样做,因为基类中的公共方法在派生类中就已经是可用的了。仅凭这一点,并不能构成使用继承足够好的理由。

你可以开发一个好的脚本或是好的IDE宏。来帮助编写这几行代码,或者使用一种更好的编程语言,环境,以支持更自动化形式的委托(比如Ruby这一点就做得不错)。

通过替换代码来扩展系统。通过替挟遵循接口契约的类,来添加井改进功能特性。要多使用委托而不是继承。

切身感受

这会让人觉得有点鬼鬼祟祟的.你可以偷偷地替抉组件代码到代码库中,而且其他代码对此一无所知,它们还获得了新的或改进后的功能。

平衡的艺术

相对继承来说,委托更加灵活,适应力也更强。

继承不是魔鬼,只是长久以来被大家误解了。

如果你不确定一个接口做出了什么样的承诺,或是有什么样的需求。那就很难提供一个对其有意义的实现了。

若有疑问请拨打北大青鸟咨询热线:010-80146691或点击免费在线咨询!
  • xml地图 网站地图 招生简章 合作企业 学员项目 联系我们
  • 关闭窗口