- 浏览: 96175 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
xiaohee:
好文章,谢谢!
XML教程——XPath实例详解 -
hapychina:
static属性和方法要分配内存,不是static的就不分配了 ...
讨论:单例和静态方法的深入讨论 -
giianhui:
就目前的工作来说,我认为很有必要
深入JVM——方法区 -
hyj1254:
深有体会啊,搞java的很难通过证明自己技术有多nb来打动面试 ...
你适合学习java吗 -
wupuyuan:
我说说我的看法吧。1、物理内存角度来说,加载代码的内存消耗在整 ...
讨论:单例和静态方法的深入讨论
在进行项目的开发活动中,有一些设计在项目刚刚开始工作的很好,但是随着项目的进展,发现需要对已有的代码进行修改或者扩展,导致这样做的原因主要有:新的功能需求的需要以及对系统进一步理解。在这个时候,我们往往会发现进行这项工作比较困难,即使能完成也要付出很大的代价。此时,一个必须要做的工作就是要对现有的代码进行重构(refactoring),通过重构使得我们接下来的工作变得相对容易。
重构就是在不改变软件系统代码的外部行为的前提下,改善它的内部结构。重构的目标就是使代码结构更加合理,富有弹性,能够适应新的需求、新的变化。对于特定问题给出优美解决方案的设计模式往往会成为重构的目标,而且一旦我们能够识别出能够解决我们问题的设计模式,将会大大简化我们的工作,因为我们可以重用别人已经做过的工作。但是在我们的原始设计和最终可能会适用于我们的设计模式间的过渡并不是平滑的,而是有一个间隙。这样的结果就是:即使我们已经知道了很多的设计模式,面对我们的实际问题,我们也没有一个有效的方法去判断哪一个设计模式适用于我们的系统,我们应该去怎样应用它。
造成上述问题的原因往往是由于过于注重设计模式所给出的解决方案这个结果,而对于设计模式的意图,以及它产生的动机却忽略了。然而,正是设计模式的意图、动机促使人们给出了一个解决一类问题的方案这个结果,设计模式的动机、意图体现了该模式的形成思路,所以更加贴近我们的实际问题,从而会有效的指导我们的重构历程。本文将通过一个实例来展示这个过程。
在本文中对例子进行了简化,这样做是为了突出问题的实质并且会使我们的思路更加清晰。思路本身才是最重要、最根本的,简化了的例子不会降低我们所展示的思路、方法的适用性。
一个完善的软件系统,必须要对出现的错误进行相应的处理,只有这样才能使系统足够的健壮,我准备以软件系统中对于错误的处理为例,来展示我所使用的思路、方法。
在一个分布式的网管系统中,一个操作往往不会一定成功,常常会因为这样或者那样的原因失败,此时我们就要根据失败的原因相应的处理,使错误的影响局限在最小的范围内,最好能够恢复而不影响系统的正常运行,还有一点很重要,那就是在对错误进行处理的同时,一定不要忘记通知系统的管理者,因为只有管理者才有能力对错误进行进一步的分析,从而查找出错误的根源,从根本上解决错误。
下面我就从错误处理的通告管理者部分入手,开始我们的旅程。假定一个在一个分布式环境中访问数据库的操作,那么就有可能因为通信的原因或者数据库本身的原因失败,此时我们要通过用户界面来通知管理者发生的错误。简化了的代码示例如下:
/* 错误码定义 */ class ErrorConstant { public static final int ERROR_DBACCESS = 100; public static final int ERROR_COMMUNICATION = 101; } /* 省略了用户界面中的其他的功能 */ class GUISys { public void announceError(int errCode) { switch(errCode) { case ErrorConstant.ERROR_DBACCESS: /* 通告管理者数据库访问错误的发生*/ break; case ErrorConstant.ERROR_COMMUNICATION: /* 通告管理者通信错误的发生*/ break; } } } |
开始,这段代码工作的很好,能够完成我们需要的功能。但是这段代码缺少相应的弹性,很难适应需求的变化。
熟悉面向对象的读者很快就会发现上面的代码是典型的结构化的方法,结构化的方法是以具体的功能为核心来组织程序的结构,它的封装度仅为1级,即仅有对于特定的功能的封装(函数)。这使得结构化的方法很难适应需求的变化,面向对象的方法正是在这一点上优于结构化的方法。在面向对象领域,是以对象来组成程序结构的,一个对象有自己的职责,通过对象间的交互来完成系统的功能,这使得它的封装度至少为2级,即封装了为完成自己职责的方法和数据。另外面向对象的方法还支持更高层次的封装,比如:通过对于不同的具体对象的共同的概念行为进行描述,我们可以达到3级的封装度- 抽象的类(在Java中就是接口)。封装的层次越高,抽象的层次就越高,使得设计、代码有越高的弹性,越容易适应变化。
考虑对上一节中的代码,如果在系统的开发过程中发现需要对一种新的错误进行处理,比如:用户认证错误,我们该如何做使得我们的系统能够增加对于此项功能的需求呢?一种比较简单、直接的做法就是在增加一条用来处理此项错误的case语句。是的,这种方法的确能够工作,但是这样做是要付出代价的。
首先,随着系统的进一步开发,可能会出现更多的错误类型,那么就会导致对于错误的处理部分代码冗长,不利于维护。其次,也是最根本的一点,修改已经能够工作的代码,很容易引入错误,并且在很多的情况下,错误都是在不经意下引入的,对于这种类型的错误很难定位。有调查表明,我们在开发过程中,用于修正错误的时间并不多,大部分的时间是在调试、发现错误。在面向对象领域,有一个很著名的原则:OCP(Open-Closed Principle),它的核心含意是:一个好的设计应该能够容纳新的功能需求的增加,但是增加的方式不是通过修改又有的模块(类),而是通过增加新的模块(类)来完成的。如果一个设计能够遵循OCP,那么就能够有效的避免上述的问题。
要是一个设计能够符合OCP原则,就要求我们在进行设计时不能简单的以功能为核心。要实现OCP的关键是抽象,抽象表征了一个固定的行为,但是对于这个行为可以有很多个不同的具体实现方法。通过抽象,我们就可以用一个固定的抽象的概念来代替哪些容易变化的数量众多的具体的概念,并且使得原来依赖于哪些容易变化的概念的模块,依赖于这个固定的抽象的概念,这样的结果就是:系统新的需求的增加,仅仅会引起具体的概念的增加,而不会影响依赖于具体概念的抽象体的其他模块。在实现的层面上,抽象体是通过抽象类来描述的,在Java中是接口(interface)。关于OCP的更详细描述,请参见 参考文献[2]。
既然知道了问题的本质以及相应的解决方法,下面就来改善我们的代码结构。
让我们重新审视代码,看看该如何进行抽象。在错误处理中,需要处理不同类型的错误,每个具体的错误具有特定于自己本身的一些信息,但是它们在概念层面上又是一致的,比如:都可以通过特定的方法接口获取自已内部的错误信息,每一个错误都有自己的处理方法。由此可以得到一个初步的方案:可以定义一个抽象的错误基类,在这个基类里面定义一些在概念上适用于所有不同的具体错误的方法,每个具体的错误可以有自己的不同的对于这些方法的实现。代码示例如下:
interface ErrorBase { public void handle(); public String getInfo(); } class DBAccessError implements ErrorBase { public void handle() { /* 进行关于数据库访问错误的处理 */ } public String getInfo() { /* 构造返回关于数据库访问错误的信息 */ } } class CommunicationError implements ErrorBase { public void handle() { /* 进行关于通信错误的处理 */ } public String getInfo() { /* 构造返回关于通信错误的信息 */ } } |
这样,我们就可以在错误发生处,构造一个实际的错误对象,并以ErrorBase引用它。然后,交给给错误处理模块,此时错误处理模块就仅仅知道一个类型ErrorBase,而无需知道每一个具体的错误类型,这样就可以使用统一的方式来处理错误了。代码示例如下:
class GUISys { public void announceError(ErrorBase error) { /* 使用一致的方式处理错误 */ error.handle(); } } |
可以看出,对于新的错误类型的增加,仅仅需要增加一个具体的错误类,对于错误处理部分没有任何影响。看上去很完美,也符合OCP原则,但是进一步分析就会发现,这个方案一样存在着问题,我们将在下一个小节进行详细的说明。
上一个小节给出了一个方案,对于只有GUISys这一个错误处理者是很完美的,但是情况往往不是这样的。前面也曾经提到过,对于发生的错误,除了要通知系统的使用者外,还要进行其他的处理,比如:试图恢复,记如日志等。可以看出,这些处理方法和把错误通告给使用者是非常不同的,完全没有办法仅仅用一个handle方法来统一所有的不同的处理。但是,如果我们在ErrorBase中增加不同的处理方法声明,在具体的错误类中,根据自身的需要来相应的实现这些方法,好像也是一个不错的方案。代码示例如下:
interface ErrorBase { public void guiHandle(); public void logHandle(); } class DBAccessError implements ErrorBase { public void guiHandle() { /* 通知用户界面的数据库访问错误处理 */ } public void logHandle() { /* 通知日志系统的数据库访问错误处理 */ } } class CommunicationError implements ErrorBase { public void guiHandle() { /* 通知用户界面的通信错误处理 */ } public void logHandle() { /* 通知日志系统的通信错误处理 */ } } class GUISys { public void announceError(ErrorBase error) { error.guiHandle(); } } class LogSys { public void announceError(ErrorBase error) { error.logHandle(); } } |
读者可能已经注意到,这种做法其实也不是十分符合OCP,虽然它把变化局限在ErrorBase这个类层次架构中,但是增加新的处理方法,还是更改了已经存在的ErrorBase类。其实,这种设计方法,还违反了另外一个著名的面向对象的设计原则:SRP(Single Responsibility Principle)。这个原则的核心含意是:一个类应该有且仅有一个职责。关于职责的含意,面向对象大师Robert.C Martin有一个著名的定义:所谓一个类的职责是指引起该类变化的原因,如果一个类具有一个以上的职责,那么就会有多个不同的原因引起该类变化,其实就是耦合了多个互不相关的职责,就会降低这个类的内聚性。错误类的职责就是,保存和自己相关的错误状态,并且提供方法用于获取这些状态。上面的设计中把不同的处理方法也放到错误类中,从而增加了错误类的职责,这样即使和错误类本身没有关系的对于错误处理方式的变化,增加、修改都会导致错误类的修改。这种设计方法一样会在需求变化时,带来没有预料到的问题。那么能否将对错误的处理方法从中剥离出来呢?如果读者比较熟悉设计模式(这里的熟悉是指,设计模式的意图、动机,而不是指怎样去实现一个具体的设计模式),应该会隐隐约约感觉到一个更好的设计方案即将出现。
让我们对问题重新描述一下:我们已经有了一个关于错误的类层次结构,现在我们需要在不改变这个类层次结构的前提下允许我们增加对于这个类层次的新的处理方法。听起来很耳熟吧,不错,这正是过于visitor设计模式的意图的描述。通过对于该模式动机的分析,我们很容易知道,要想使用visitor模式,需要定义两个类层次:一个对应于接收操作的元素的类层次(就是我们的错误类),另一个对应于定义对元素的操作的访问者(就是我们的对于错误的不同处理方法)。这样,我们就转换了问题视角,即把需要不同的错误处理方法的问题转变为需要不同的错误处理类,这样的结果就是我们可以通过增加新的模块(类)来增加新的错误处理方法,而不是通过增加新的错误处理方法(这样做,就势必要修改已经存在的类)。
一旦到了这一部,下面的工作就比较简单了,因为visitor模式已经为我们搭建了一个设计的上下文,此时我们就可以关注visitor模式的实现部分来指导我们下面的具体实现了。下面仅仅给出最终的程序结构的UML图以及代码示例,其中忽略了错误类中的属于错误本身的方法,各个具体的错误处理方法通过这些方法和具体的错误类对象交互,来完成各自的处理功能。
最终的代码示例
interface ErrorBase { public void handle(ErrorHandler handler); } class DBError implements ErrorBase { public void handle(ErrorHandler handler) { handler.handle(this); } } class CommError implements ErrorBase { public void handle(ErrorHandler handler) { handler.handle(this); } } interface ErrorHandler { public void handle(DBrror dbError); public void handle(CommError commError); } class GUISys implements ErrorHandler { public void announceError(ErrorBase error) { error.handle(this); } public void handle(DBError dbError) { /* 通知用户界面进行有关数据库错误的处理 */ } public void handle(CommError commError) { /* 通知用户界面进行有关通信错误的处理 */ } } class LogSys implements ErrorHandler { public void announceError(ErrorBase error) { error.handle(this); } public void handle(DBError dbError) { /* 通知日志系统进行有关数据库错误的处理 */ } public void handle(CommError commError) { /* 通知日志系统进行有关通信错误的处理 */ } } |
设计模式并不仅仅是一个有关特定问题的解决方案这个结果,它的意图以及它的动机往往更重要,因为一旦我们理解了一个设计模式的意图、动机,那么在设计过程中,就很容易的发现适用于我们自己的设计模式,从而大大简化设计工作,并且可以得到一个比较理想的设计方案。
另外,在学习设计模式的过程中,应该更加注意设计模式背后的东西,即具体设计模式所共有的的一些优秀的指导原则,这些原则在 参考文献[1]的第一章中有详细的论述,基本上有两点:
- 发现变化,封装变化
- 优先使用组合(Composition),而不是继承
如果注意从这些方面来学习、理解设计模式,就会得到一些比单个具体设计模式本身更有用的知识,并且即使在没有现成模式可用的情况下,我们也一样可以设计出一个好的系统来。
<!-- CMA ID: 53318 --><!-- Site ID: 10 --><!-- XSLT stylesheet used to transform this file: dw-article-6.0-beta.xsl -->
文章来源:http://www.ibm.com/developerworks/cn/java/l-dpstruct/part1/index.html?ca=drs-
发表评论
-
设计模式之Visitor模式的应用(二)
2010-12-01 11:43 12751、 回顾 在上一篇文章中,我们给出了一个使用设计模式来改善 ... -
XML教程——XML四种解析器的比较
2010-11-26 14:21 10061: DOM DOM 是用与平台 ... -
XML教程——JAXP详解(二)
2010-11-25 19:54 1605在 JAXP 的早期版本中,该首字母缩写代表 Java API ... -
XML教程——JAXP详解(一)
2010-11-25 19:52 1943Java 技术和 XML 无疑是最 ... -
XML教程——XPath实例详解
2010-11-25 19:43 2892如果要告诉别人买一加仑牛奶,您会怎么说?“请去买一加仑牛奶回来 ... -
XML教程——采用DOM来解析XML
2010-11-25 15:35 892DOM基础 在开始使用 DOM ... -
XML教程——采用SAX来解析XML
2010-11-25 15:33 1314什么是 SAX 读取和操纵 XML 文件的标准方法是 DOM ... -
XML教程——XML解析器
2010-11-25 15:30 1638Parser基础 一个 XML Parser ... -
XML教程——XML Schema
2010-11-25 10:22 1227什么是XML Schema XML Schema如同DTD一 ... -
XML教程——XPath
2010-11-25 10:01 1005用XPath精确定位节点元 ... -
XML教程——XSL样式
2010-11-24 20:46 1500什么是样式单 对于一 ... -
XML教程——命名空间
2010-11-24 20:34 1117为何引入名称空间? XML的用途不是单一的。虽然读者可能看到 ... -
XML教程——DTD
2010-11-24 20:29 1030内部Dtd 一个“有效的 ... -
XML教程——XML简介
2010-11-24 20:22 956什么是XML XML即为可扩 ...
相关推荐
访问者模式(Visitor)是一种行为设计模式,它允许在不修改对象结构的前提下向对象结构中的元素添加新的操作。这种模式将算法与数据结构分离,使得算法可以独立于数据结构进行变化,增强了系统的可扩展性。 在C++中...
"设计模式系列之visitor"是一个关于软件设计模式的讨论,特别是关注于“访问者”(Visitor)模式。这个模式是GOF(Gamma, Helm, Johnson, Vlissides)在他们的经典著作《设计模式:可复用面向对象软件的基础》中提出...
**C#设计模式之Visitor** **一、设计模式概述** 设计模式是软件开发中的经验总结,它提供了解决常见问题的可复用解决方案。在C#编程中,设计模式可以帮助我们编写更灵活、可扩展和易于维护的代码。"Visitor"(访问...
访问者模式是一种行为设计模式,它使你可以在不修改对象结构的情况下,为对象添加新的操作。这种模式的核心在于将数据结构与对这些数据的操作解耦,使得增加新的操作变得容易,同时也允许独立地改变元素类和访问者类...
1. 在Web开发中,Spring框架就广泛应用了设计模式,如工厂模式用于创建Bean,单例模式保证每个Bean只有一个实例,观察者模式用于事件驱动编程,策略模式用于实现AOP(面向切面编程)。 2. 在游戏开发中,状态模式常...
《设计模式——Java语言中的应用》是一本专为Java开发者深入理解面向对象设计而编写的经典书籍。设计模式是软件工程领域中经过实践验证的、解决常见问题的有效方案,它们代表了在特定上下文中,针对特定问题的最优...
《Java设计模式之禅》是一本深入浅出讲解设计模式的书籍,书中不仅包含23种经典设计模式的案例,还详细介绍了设计模式背后的思想和原则,适合初学者以及对设计模式有一定了解的程序员阅读。本书旨在帮助读者理解如何...
**访问者模式(Visitor Pattern)**是一种行为设计模式,它提供了一种在不修改对象结构的情况下增加新操作的方法。这种模式的主要思想是将数据结构与算法分离,使得算法可以在不改变对象结构的情况下独立变化。 在...
### C#面向对象设计模式纵横谈(24):(行为型模式) Visitor 访问者模式 ...通过上述资源的学习和实践,开发者可以更加熟练地掌握**Visitor模式**及其他设计模式的应用技巧,从而编写出更加灵活、可扩展和高质量的代码。
2. **访问者模式**:这是与Visitor模式相关的另一种设计模式,但在这里,我们可能是指在表达式树的节点上应用访问者模式。访问者模式使得可以在不修改节点类的情况下,增加对节点的新功能。在本项目中,可能是通过在...
### 设计模式-访问者(Visitor)模式详解和应用 #### 一、访问者模式简介 访问者模式(Visitor Pattern)是一种行为型设计模式,它允许我们向一组已存在的类添加新的行为,而无需修改这些类。这种模式的核心思想是在...
Java设计模式是软件开发中的一种经验总结,它们是解决特定问题的模板,可以在不同情境下重用,以提高...理解和掌握设计模式,可以帮助开发者写出更加优雅、高效、易于维护的代码,是成为优秀Java程序员的关键技能之一。
访问者模式(Visitor)是一种行为设计模式,它允许在不修改对象结构的前提下向对象结构中的元素添加新的操作。这种模式的核心思想是分离了算法和对象结构,使得算法可以在不改变对象结构的情况下独立变化。 访问者...
设计模式是软件工程中的一种重要概念,它代表了在特定情境下解决问题的可重用解决方案。《设计模式那点事》这本书的PPT为我们提供了一种深入理解和学习设计模式的途径。在这里,我们将深入探讨设计模式的核心概念、...
《C++设计新思维:泛型编程与设计模式之应用》这本书深入探讨了C++语言在泛型编程和设计模式中的应用,对于理解和提升C++编程能力有着重要的指导价值。以下将围绕这些主题展开详细讨论。 一、泛型编程 泛型编程是...
设计模式之 Visitor(访问者) 访问者在进行访问时,完成一系列实质性操作,而且还可以扩展. 设计模式引言 设计面向对象软件比较困难,而设计可复用的面向对象软件就更加困难。你必须找到相关的对象,以适当的粒度将...
文档中的“23种设计模式学习笔记.doc”可能包含了对这23种模式的详细解释和实例,而“设计模式之我爱我家.doc”可能从一个更生活化的角度来阐述设计模式的概念。“软件23种设计模式,超级经典的.pdf”可能是对这些...
设计模式不仅仅是一系列具体的解决方案,更是一种思维方式。它们提供了通用的模板,帮助开发者在遇到特定问题时能够快速找到有效的解决方案。设计模式的学习过程通常分为四个阶段:学习、表达、教授、记录。每个阶段...
软件设计模式和软件体系结构知识点总结 software design patterns and software architecture knowledge points summary 创建型软件设计模式: 工厂模式(Factory Pattern):符合开闭原则,提供了一种创建对象的...