长空待击

« Previous day (四月 1, 2006) | Main | Next day (四月 3, 2006) »

http://blog.matrix.org.cn/xMatrix/date/20060402 星期日 2006年04月02日

[译文]对话Eric Gammar--设计模式的设计准则(下)


什么时候该考虑接口
Bill Venners: 关于接口,GoF书包含了一些UML类图,这些看起来好像混和了接口和实现。当你看的时候,看到的是代码的设计,而这很难分清楚什么是API什么是实现。相反地如果你看一下JavaDoc,你只会看到接口。另外我也觉得在XP看不到接口和实现的区别,好像XP只讨论代码。了解到你正在改变这种测试驱动开发的无组织状态,那么什么时候开发人员应该考虑接口而不是代码呢?
Erich Gamma: 在设计一个应用和你设计一个平台时你需要区别对待。在设计平台时,你必须关心什么是API,什么是内部的。现在的重构使得修改名字变得很容易因而你需要小心避免意外的修改已发布的API。这已经不只是定义哪种类型需要被发布,你还需要回答下面的问题:你是否允许客户继承你的类?在Eclipse的API中,我们尽力标明是否我们允许客户来继承某一个类。我们的团队中有一个API提倡者,他帮助我们不仅遵守规则而且使API保持一致。
而设计应用时,即使你提取出拥有多种变化的抽象。对设计来说,你需要的是给出关键抽象,然后让其他的代码与这些抽象交互,而不是特定的实现,这样你就拥有了灵活性。当一个抽象的新变化出现时,代码依然可以正常运行。对XP来说,就像我前面说的,流行的重构工具可以使你在代码中尽早地引入接口,因而XP也是一致的。
Bill Venners: 那就是说应用与平台是一样的思考方式,只不过少一些可伸缩性。另一个不同就是如果我控制了所有与这个接口相关的客户那么对我来说是不是就很容易根据需要改变接口了。
Erich Gamma: 是的,对应用来说思考方式是一样的。你也需要构建应用使其用得更久。响应需求的变更不应该影响整个应用。如果一旦你提供出去了代码而你不再可以访问所有的客户端,那么你就必须以API的角度来考虑了。
Bill Venners: 即使这些客户是由同一公司的不同部门开发的吗?
Erich Gamma: 是的,就是这样。
Bill Venners: 这听起来对接口的思考在项目需要扩展时变得更重要了。如果项目只有两三个人,那么接口就不那么重要了,因为你可以想修改就修改。重构工具可以...
Erich Gamma: 重构工具可以为你做这些。
Bill Venners: 但如果是100个人的团队,这意味着大家会被分为多个组,不同的组会有不同的责任区。
Erich Gamma: 我们遵循的一个实践是将组件分配给一个组,而这个组负责这个组件并且发布相关的API,然后通过API来定义依赖。我们来抵制那种定义如一些组件比其他的更适合内部使用这样的关系。在Eclispe中所有组件是平等的,例如,Java开发工具插件并不比其他插件有更多特权。
一旦你发布了API那么你就需要负责保持其稳定性,否则就会破坏其他组件使项目进程受到阻碍。在这种规模大小的项目中保持API的稳定性是一个项目继续的关键要素。
在一个封闭环境中,当变化发生时你需要更多的灵活性。例如,你可以使用Java的废弃功能来允许其他团队逐步适应你的修改。而当大家意见一致时就可以删除废弃的方法了,但这不太可能发生在一个开放的平台中,废弃的方法是不能被删除的因为会破坏客户的应用。

组合PK继承
Bill Venners: GoF书中描述的OO设计的另一个准则是“尽量使用组合而不继承”。这是什么意思,为什么需要这样做呢?
Erich Gamma: 即使10年后的今天我依然坚持这是需要的。继承是一种改变行为的很酷的方式,但我们知道这种方式是脆弱的,因为子类很容易假定他重写的方法的上下文。而这样就将基类与其子类紧耦合了,因为子类代码的上下文会被使用。而组合有一种更好的属性,通过一些小的方式来插入大的对象就减少了耦合,大的对象最后会返回给小的对象。从API的观点来年,可被重写的方法比可被调用的方法有更强的义务。
在子类中当重写的方法被调用时你可以假定父类的内部状态,当你只是嵌入一些行为,这会更简单些。但这也是为什么你应该尽量使用组合的原因。一种普遍的误解是组合不能使用继承,但实际上组合就是使用了继承,但通常你只是实现了一个小的接口而不是继承自一个大的类。Java的监听器方式就是一个好的例子,通过监听器你实现一个监听器接口或者继承称谓适配器的接口。然后你创建一个监听器对象并注册到一个按钮中,而这些并不需要继承按钮类来响应事件。
Bill Venners: 当我在我的设计课程中讨论设计模式时,我提及最主要的是使用接口继承式的组合来处理不同情况。我说的接口继承,指继承如C++中的纯虚基类或者说Java中的接口继承。你提及的监听器例子也是这种情况,我实现了MouseListener,当我通过AddMouseLIstener传递实例给Jpanel时,我就是在用组合,因为前端拥有MouseListener的JPanel会调用他的mouseClicked方法。
Erich Gamma: 是的,你已经减少了耦合。此外这时你已经有了分离的监听器对象,你甚至可以用另的对象也联结他。
Bill Venners: 组合优于继承的其他灵活性还有些是我很难解释的。下面是一些我希望你能自己理解的一些短语:为什么?真正有用的是什么?额外的灵活性到底来自什么地方。
Erich Gamma: 我们称这种为黑箱重用。有一个容器,然后你嵌入一些小的对象,这些小对象配置容器并且自定义容器的行为,因为容器代理了一些行为给小的接口,因此这种方式是可行的,最后你就可以通过配置来自定义行为了。这就为你提供了灵活性和小对象的重用机会,而这是最有用的。相对于给你冗长的解释,我更喜欢用策略模式来指点你。这也是我通常解释组合优于继承的原型例子。额外的灵活性来自于你可以嵌入不同的策略对象,甚至你可以在运行时动态地改变策略对象。
Bill Venners: 那么如果打算使用继承...
Erich Gamma: 那么你不能综合策略模式对象的优点,也不在运行时动态地实现了。


Valid HTML! Valid CSS!

This is a personal weblog, I do not speak for my employer.