扩展型模式介绍

  本章介绍面向对象程序设计的一些基本原则,借助这些原则,你可以进一步更加客观地评估自己的代码。

  除了扩展代码库的常见技术之外,也许希望使用设计模式来添加新功能。在了解常规设计中面向对象的主要原则之后,本章将讨论以前包含扩展功能的设计模式,并在本部分介绍其他面向扩展的设计模式。

 

1.面向对象设计原则:

  有很多讨论设计原则的论坛,其中最著名的一个论坛是Portland Pattern Responsitory(www.c2.com[Cunningham])。如果浏览这个网站,你会发现有些原则已经在OO设计中体现出价值。在进行自己的OO设计景,尤其应该注意使用的是Liskov替换原则(LSP)。

2.Liskov替换原则(LSP):

  新类对超类的扩展应该合乎逻辑,并且具有一致性。但是什么样的扩展才算是合乎逻辑并且一致呢?Java编译器能够确保一定级别的一致性,但是许多一致性原则都会绕过编译器。其中有个原则,我们应该在自己的设计中加以考虑,它就是Liskov替换原则,其定义为:一个类的实例应该具有其超类的所有功能。

  面向对象语言,如Java,可以提供对LSP的基本支持。例如,由于UnloadBuffer对象是Machine类的一个子类,我们可以将UnloadBuffer对象当作一个Machine对象来引用,这样做是合法的:

Machine m = new UnloadBuffer(3501);

 

为保证设计能够遵循LSP的某些方面,常常需要借助开发人员的智慧,因为至少现在的编译器还无法提供这种支持。我们看看图1:


本图可以回答如下问题:卸载缓冲池是机器吗?圆形是个椭圆吗?

 

卸载缓冲池肯定是个机器,但是在类层次中模拟这个问题就会出现问题。对于Oozinoz公司的机器而言,除了卸载缓冲池之外,每个机器都可以接收化学药品车。由于几乎每台机器都可以接收化学药品车,并且可以报告机器所拥有的车辆集合,因此把这个功能迁移到Machine类是非常有价值的。但是对UnloadBuffer对象调用addTub()和getTub()方法会出现问题。如果出现问题,我们是否应该在调用这些方法时抛出异常?

 

 假设还有一个开发者编写一个方法可以询问某车间中所有的机器,以便于获取该车间中所有化学药品车的完整列表。当用这个方法处理卸载缓冲池时,如果UnloadBuffer类中的getTubs()方法抛出异常的话,这部分代码就会出现异常。这样会严重违反LSP:把UnloadBuffer对象作为Machine对象来使用,程序会崩溃!如不想抛出异常,只需要让代码忽略对UnloadBuffer类中getTubs()和addTubs()方法的调用。这样做仍旧会违反LSP:如果给某机器添加一个材料箱,这个材料箱也许会消失!

  违反LSP并不一定完全是程序设计的缺陷。对于Oozinoz机器而言,让Machine类拥有应用于大多数机器的行为,还是不违反LSP原则,其中具体利弊,设计者自己判断。重要一点是要认识到LSP,并且能够了解为什么其他有的设计思路会违反LSP。

 

突破题:圆形肯定是一种特殊的椭圆,是不是?请说明上图中的Ellipse和Circle类之间的关系是否违反LSP?

答:数学中,圆形是椭圆形的特殊情况。但是在OO编程中,椭圆对象的行为与圆形对象的行为不同。比如,一个椭圆的高度可能是宽度的2倍。但是圆形是不可能的。如果这种类似行为对于程序很重要,那么Circle对象不会看起来像Ellipse对象,这是对LSP的违反。

   请注意,如果你考虑不可变对象,这也许不适合 。这仅仅是个数学问题,不适合标准的类型层次结构。

 

 

3.Demeter法则:

  80年代后期,美国东北大学Demeter Project的成员尝试把能够保证优异的面向对象设计的规则制度化.项目组成员把这些规则称为Demeter法则(Law of Demeter,LOD).在Assuring Good Style for Object-Oriented Programs一文中,Karl Lieberherr和Ian Holland[1989]全面总结了这些规则,其中说到"概括地说,Demeter法则要求每个方法只能给有限的对象发送消息,这些对象包括:参数对象、this伪变量,以及this变量的后继子对象。“本文还给出法则的正式定义。相对于完全掌握法则的意图而言,指出设计是否符合LOD法则更容易些。

 假设有一个MaterialManager对象,该对象的一个方法把Tub对象作为接收参数。Tub对象有Location属性,返回表示材料箱所在位置的Machine对象。假设在MaterialManager方法中,你需要知道机器是否已打开并且可用。如下代码也许会出现在自己编写的方法中:

if(tub.getLocation().isUP()) { //... }

 

这部分代码违反了LOD,原因在于它调用一个方法,向tub.getLocation()发送消息。tub.getLocation()不是参数,不是this---MaterialManager对象的方法正在执行---并且不是this的属性。

 

突破题:请说明为什么tub.getLocation().isUp()语句被视为不合适的?

答:如果tub对象的location属性有微妙变化,表达式tub.getLocation.isUp()也许会导致程序失效。比如,Location属性也许是null,或者是某Robot对象。如果Location属性是null,计算tub.getLocation().isUp()的值就会抛出异常。如果Location是一个Robot对象,问题会变得更糟,因为我们尝试使用一个机器人去从自身搜索材料箱。这些潜在问题是可以管理的,但是我们希望这些代码位于使用tub.getLocation().isUp()表达式的方法中吗?不是的,需要的代码也许已经存在于Tub类!如果不是的话,就应该把它放在Tub类中,以防在其他地方重复编写这些代码!

 

  如果这个突破题让大家意识到LOD只把形式为a.b.c的语句当作是错误的,那么就轻视了LOD的价值。实际上,Lieberherr和Holland希望LOD能够更加深入,可以完全解决类似问题。那么在编写面向对象代码时,是否有一些可以遵循的固定公式或者法则?我建议最好阅读LOD的原文,这样能够理解得更透彻一些。像LSP一样,Demeter法则有助于编写更好的代码,并且知道你的代码何时违反了规则。

  你也许发现在遵循这些掼后,编写的程序质量确实比较好。但是对很多开发者而言,OO开发仍旧是艺术。对代码库的艺术性扩展需要艺术大师总结的规范,但是同时,这些所谓的大师也仍旧在尝试中。重构指的是用于代码修改的工具集合,可以改进代码质量,而不会影响其功能

 

4.消除代码坏味

  你也许相信LSP和LOD能够防止编写糟糕的代码,而实际上更大的可能是,使用这些规则可以帮助你找到糟糕的代码,并进行修复。这是我们每天重复的工作:编写代码,使之可以运行机制,发现和修改代码的质量问题,改进代码质量。但是发现问题的准确度有多大呢?形象的说,答案就是关于发现代码的坏味。《重构》[Fowler et al.,1999]一书介绍了22种代码质量低下的特征,通过多次对应的重构就可以逐步改善代码质量。

 

突破题:提供一个存在“坏味”的方法代码,需要进行改进,但改进时不能违反LSP和LoD.

答:对于下面的范例:

public static String getZip(String address) 
{ 
return address.substring(address.length()-5); 
}

 

部分代码存在问题,包括原始迷惑,指使用字符串来尝试包含多个属性。

 

 

 

5.超越普通的扩展

  很多设计模式,包括本书已经介绍的那些设计模式,其目标都与扩展类的行为有关。面向扩展的模式通常明确两种开发者角色。比如,在Adapter模式中,一个开发者可设计其他开发者需求的服务,服务是通过对象接口提供的。

 

突破题:填充如下表格的空白之处:提供使用设计模式来扩展类或者对象的行为的范例。

 

                                                     设计模式

焰火模拟设计者建立一个接口,这个接口定义你的对象必须拥有的行为,以便于完成模拟

Adapter模式

允许你在运行时组合可执行对象的工具集

Interpreter模式

超类的一个方法要求子类来完善程序步骤

Template Method模式

对象允许你扩展其行为,方法是:接收封装在一个对象中的方法,在合适的时间调用这个方法

Command模式

代码生成器插入行为,提供在其他机器上执行的对象在本地执行的假想

Proxy模式

某设计思路允许你注册一个回调函数,当某对象变化时,该函数会被调用

Observer模式

设计允许你定义依赖于已定义接口的抽象操作,并且允许你添加完成本接口的新驱动器

Bridge模式

 

除了已经讨论的模式之外,还有三个设计模式的目标主要在于扩展。

 

            如果你期望                                    可应用模式

允许开发者动态组合对象的行为

Decorator模式

提供顺序地访问集合元素的方法

Iterator模式

允许开发者定义新操作,而无需更改类层次

Visitor模式

 

6.小结

  面向对象编程的出现带来了一个重要变化,那就是现在的编程已经变成在现有代码库的基础上进行扩展,然后根据设计模式改进代码质量。评估代码质量的完整而客观的标准和技术并不存在,但是前人总结的基本原则可以帮助我们改进OO代码质量。

  LSP建议类的实例看起来像其超类的实例。你应该注意到并且能够判断代码是否违反LSP。LoD是一个规则集合,遵循该规则可以帮助你减少类之间的依赖关系,获得更加整洁的代码。

   Martin Fowler et al.[1999]总结了糟糕代码的大量表现特征。每种代码“坏味”都可以通过重构来消除,有时需要对设计模式进行重构。很多设计模式可以用来标识、简化或者方便扩展。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值