本章介绍面向对象程序设计的一些基本原则,借助这些原则,你可以进一步更加客观地评估自己的代码。
除了扩展代码库的常见技术之外,也许希望使用设计模式来添加新功能。在了解常规设计中面向对象的主要原则之后,本章将讨论以前包含扩展功能的设计模式,并在本部分介绍其他面向扩展的设计模式。
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]总结了糟糕代码的大量表现特征。每种代码“坏味”都可以通过重构来消除,有时需要对设计模式进行重构。很多设计模式可以用来标识、简化或者方便扩展。
- 大小: 3.4 KB
分享到:
相关推荐
《java设计模式(第2版)》通过一个完整的java项目对经典著作design patterns一书介绍...第26章 扩展型模式介绍 236 第27章 装饰器(decorator)模式 242 第28章 迭代器(iterator)模式 259 第29章 访问者(visitor)模式 278
### 5G NR无线网络扩展型皮站技术要求——硬件分册 #### 一、概述 随着5G技术的快速发展,扩展型皮站作为5G网络的重要组成部分,在提高室内覆盖和增强用户体验方面扮演着关键角色。《5G NR无线网络扩展型皮站技术...
全书分为七个部分,涵盖了设计模式的基本介绍、创建型模式、结构型模式、行为型模式、小颗粒度基础模式的应用案例、应用全局的模式化实现方法以及针对Web和Web Service领域的设计技巧。 #### 第一部分:概述性介绍 ...
设计模式分为三大类:创建型模式、结构型模式和行为型模式。 **创建型模式**关注的是对象的创建。共有五种创建型模式: 1. **工厂方法模式**:它定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法...
### JAVA设计模式详解:创建型、结构型、行为型模式介绍及代码示例 #### 一、概述 在软件开发过程中,设计模式提供了一系列解决常见问题的模板,帮助开发者更高效地编写高质量代码。设计模式通常被划分为三种类型...
这个PDF文档“24种设计模式介绍与6大设计原则”旨在帮助开发者理解和应用这些模式,提高代码的可维护性和可扩展性。以下是其中的主要内容: 一、设计原则 1. 单一职责原则:一个类或模块应只有一个引起其变化的原因...
创建型模式作为设计模式的五种主要类别之一,它主要关注的是对象的创建过程,通过抽象和封装创建过程中的变化点,使得系统能够更加灵活地应对未来的变化。 创建型模式包括多种不同的模式,例如工厂模式、单态模式、...
在本文中,我们将深入探讨结构型设计模式,特别是桥接模式、适配器模式、装饰者模式和组合模式,以及它们在实际场景中的应用。 1. **桥接模式**: 桥接模式将抽象部分与实现部分分离,使得它们可以独立进行变化。...
JAVA 24种设计模式主要分为三类:创建型模式、结构型模式和行为型模式。创建型模式关注对象创建机制,为对象实例化提供指导,包括单例模式、抽象工厂模式、建造者模式、工厂方法模式、原型模式和生成器模式等。结构...
Singleton 模式是一种创建型模式,用于限制类的实例化次数,确保只有一个实例。Singleton 模式的优点是可以节省内存空间,因为只需要创建一个实例就足够了。 Singleton 模式的缺点是它可能会导致代码的耦合度增加。 ...
1. **工厂模式**:这是最简单的创建型设计模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们创建一个工厂类,该类负责创建对象,而客户端代码不再直接创建对象,而是通过工厂类来获取。这种模式在扩展时...
Bridge模式是一种设计模式,属于结构型模式之一,其主要目的是将抽象部分与实现部分分离,使得两者可以独立地进行变化。这种模式的核心思想是“抽象不应该依赖于具体,而应该依赖于抽象”。Bridge模式通过引入一个...
工厂模式是一种创建型设计模式,它提供了一种创建对象的最佳方式,通过抽象工厂类,使得创建对象的过程独立于客户端,增强了系统的灵活性。 2. MarvelousWorks.PracticalPattern_28 可能是“单例模式”的实现。单例...
虽然描述中没有具体提及结构型模式的实例,但常见的结构型模式有代理模式、装饰器模式、桥接模式等,它们都在不同的场景下帮助我们构建更灵活、可扩展的系统。 **创建型模式**主要处理对象的创建。这些模式提供了一...
接着,书中会逐一解析创建型模式(如工厂方法、抽象工厂、单例等)、结构型模式(如适配器、装饰器、代理等)和行为型模式(如策略、观察者、责任链等)。每种模式都会结合C#代码实例进行详细阐述,帮助读者更好地...
在给定的压缩包文件中,我们关注的是结构型设计模式,这些模式主要用于处理类和对象的组合与结构,以实现更灵活、可扩展的设计。下面我们将详细探讨其中涉及到的几个模式:桥接模式、适配器模式、装饰者模式和组合...
行为型模式则关注对象之间的交互和职责分配,如策略模式(Strategy)、观察者模式(Observer)和责任链模式(Chain of Responsibility)等,它们有助于提高代码的灵活性和可扩展性。 在C#中,设计模式的实现往往...
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 行为型模式,共十一种:...
今天我们要探讨的是“Java设计模式之结构型模式”。结构型模式主要关注如何组织类和对象,以达到良好的架构,提升代码的可读性和可维护性。在这个主题中,我们将深入理解并讨论这些模式的原理、应用场景及其实现。 ...