`
躁动的绵羊
  • 浏览: 95896 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

设计模式之Visitor模式的应用(二)

阅读更多

1、 回顾

在上一篇文章中,我们给出了一个使用设计模式来改善程序结构的例子,着重介绍了设计模式的意图、动机在我们程序重构过程中的指导作用。

现在,我们将关注设计模式的另一个重要方面:设计模式的适用性。解决同一个问题一般会有多种方案或者模式,但是这些模式所关注的是同一个问题的不同方面,解决不同的需求,有各自的优点和限制,各有各的解决之道。这就要求我们在选择设计模式时,对我们自己的问题有很好的理解:我们的需求是什么,我们要克服什么样的限制,我们要获得什么样的特性等等。然后,可以看看我们想使用的解决问题的设计模式是否适用于我们的问题,如果不适用,是否可以使用其他的模式来弥补,是否可以对这个设计模式进行改进使它符合我们的要求。

本文下面的部分,我们将对上一篇文章中的最终解决方案进行进一步的分析,来看看它到底满足了我们什么样的需求,又暴露了什么样的不足,最后我们会给出一个更为符合要求的解决方案。


2、 问题描述

在上一篇文章中,我们对一个网管系统中的错误处理部分的代码进行了重构,最终使用了一个visitor设计模式解决了我们的问题。细心的读者肯定会发现,这个最终方案一样存在着一个问题:如果错误的类型不是固定不变的,而是随着系统的进展不断增加的,会有什么结果呢?让我们首先来看看上一篇文章中最终的类图:


图1

在这个类图中,我增加了几条依赖线,即ErrorHandler是依赖于DbError和CommError的。此时我们可以看到:ErrorBase依赖于ErrorHandler,ErrorHandler依赖于ErrorBase的派生类(DbError和CommError),而ErrorBase的派生类又要依赖于ErrorBase本身。这就形成了一个循环的依赖过程,这样的结果就是ErrorBase传递地依赖于它的所有派生类。

这种循环依赖关系会带来严重的问题,一旦ErrorBase新增了一个派生类,那么ErrorHandler类必须要被修改,由于ErrorBase又依赖于ErrorHandler,那么依赖于ErrorBase的所有类都需要重新编译。这就意味着ErrorBase的所有派生类以及所有这些派生类的使用者都要重新编译,这种大规模的重新编译在开发一个分布式系统时会导致非常大的工作量,因为要重新分布每一个重新编译过的类,如果在重新分布时出现一些差错(如:忘记替换一些类),就会导致微妙的错误,而且很不容易查找出来。

另外,在该模式中存在一个假设,就是默认任意一个错误处理者要处理所有的错误类型,这个假设在某些情况下是不成立的,比如:如果对于LogicError我们不打算通知LOGSys进行处理会怎样呢?我们不得不要写一个处理该错误的空函数(当然你可以在ErrorHandler中写一个缺省的实现)。如果ErrorBase的类层次架构越来越大,并且它们要求的处理方法也有很多的不同,就会导致ErrorHandler接口中的方法集庞大,并且任何一个ErrorBase的派生类的改变,都会导致大规模的重新编译(甚至是毫无关系的类也要重新编译),重新分布,如果这种改变比较频繁,结果当然是无法忍受的。


3、 问题分析

上述的问题描述暴露了visitor模式的一些使用限制,即它仅仅适用于哪些受访问的类层次架构比较固定的情况,导致这样的原因可以使用一个著名的面向对象设计原则来解释,这个原则就是:DIP(Dependence Inversion Principle),这个原则的核心含义是:高层模块不应该直接依赖于低层模块,所有的模块都要依赖于抽象。也就是说:容易变化的东西一定要依赖于不容易变化的东西,因为抽象的东西最不容易变化,具体的东西容易变化,所以具体应该依赖于抽象。而在visitor模式中,ErrorHandler依赖于ErrorBase的所有的具体的派生类,并且如果这些派生类容易变化的话,就会导致不能接受的结果。

通过上面的分析,可以看出打断这个循环依赖的环是克服visitor模式适用范围限制的关键。


4、 解决方案

在上述的循环依赖关系中,有两段依赖关系是无法打断的,一段是ErrorBase的所有派生类对于ErrorBase的依赖,一段是ErrorBase对于ErrorHandler的依赖,并且这两段依赖关系也是符合DIP的,那么对于仅剩的一段依赖关系我们是必须要打断的了,这段关系就是,ErrorHandler对于ErrorBase所有派生类的依赖,并且这段关系也是违反DIP的。如果我们不让ErrorHandler知道ErrorBase的派生类,那么怎样才能够针对每一个具体的ErrorBase的派生类进行相应的处理呢?

面向对象大师Robert C. Martin给出了一个优雅的解决方案,他所采用的技巧是OO方法所建议避免使用的,RTTI(运行时类型鉴别)。可见如果RTTI运用的得当,一样可以得到很好的设计方案,并且还能够克服一些OO中多态的方法解决不了的问题(当然如果使用多态能够解决的问题,推荐还是使用多态的方法进行解决)。让我们先看看这个解决方案的类图:


图2

通过上图可以看出,ErrorHandler中没有任何方法了,已经退化为一个空的接口,所以也就不可能再依赖于任何ErrorBase的派生类了。和visitor模式不同,这个方案中针对每一个特定的ErrorBase的派生类定义一个相应的处理接口,在每个派生类的handle方法实现中,运用RTTI技术进行相应的类型转换(把ErrorHandler转换为自己对应的错误处理接口,比如:在DbError的handle方法中,就把ErrorHandler转换为DbErrorHandler。Java在这方面做得不错,可以进行比较安全的类型转换),想要处理该错误的实体不仅要实现ErrorHandler接口,而且还要实现相应的针对具体错误类的处理接口,比如:GUISys就实现了三个接口(ErrorHandler、DBErrorHandler以及CommErrorHandler),从而也就实现了对DbError和CommError的处理。

这种方法打断了类之间的循环依赖关系,使得增加新的错误类型变得容易,并且也避免了可能出现的大规模的重新编译以及类的重新分布。并且,你也可以有选择地进行错误的处理,比如:如果GUISys不想处理DbError的话,很简单,不要实现DbErrorHandler接口即可,使得程序的结构非常清晰。可见这个方案克服了原始的visitor设计模式的不足。

通过对比上下两个类图可以看出,下面的要比上面的复杂,这也是该方案的一个缺点。如果问题的规模不大,重新编译以及重新分布没有多少工作量的话,还是可以使用原始的visitor模式的。仅仅当问题的规模扩大到visitor模式不能适用的地步时,可以考虑使用该方案。另外,由于改进的方案中使用了RTTI技术,会导致性能上的损失以及不可预测性,在使用时也要特别注意。

我们做如下的类比以便于更好地理解原始的visitor模式和改进后的方案。原始的visitor模式好像一个矩阵,在X方向上是一个个具体的错误类型,在Y方向上是一个个可以处理错误的实体,每一个交叉点上是具体的处理方法,矩阵中的每一个位置上都必须有一个处理方法。而改进后的方案象一个稀疏矩阵,仅仅在需要的位置上才有具体的处理方法,从而减少了很多冗余。

下面给出关键的代码片断:

interface ErrorBase
{
    public void handle(ErrorHandler handler);
}
class DBError implements ErrorBase
{
    public void handle(ErrorHandler handler) {
        try {
            DbErrorHandler dbHandler = (DbErrorHandler)handler;
            dbHandler.handle(this);
        }
        catch(ClassCastException e) {
        }
    }
}
class CommError implements ErrorBase
{
    public void handle(ErrorHandler handler) {
        try {
            CommErrorHandler commHandler = (CommErrorHandler)handler;
            commHandler.handle(this);
        }
        catch(ClassCastException e)\ {
        }
    }
}
interface ErrorHandler 
{
}
interface DbErrorHandler
{
    public void handle(DBrror dbError);
}
interface CommErrorHandler
{
    public void handle(CommError commError);
}
class GUISys implements ErrorHandler, DbErrorHandler, CommErrorHandler
{
    public void announceError(ErrorBase error) {
        error.handle(this);
    }
    public void handle(DBError dbError) {
 		/* 通知用户界面进行有关数据库错误的处理 */       
    }
    public void handle(CommError commError) {
    		/* 通知用户界面进行有关通信错误的处理 */       
    }
}
class LogSys implements ErrorHandler, DbErrorHandler, CommErrorHandler
{
    public void announceError(ErrorBase error) {
        error.handle(this);
    }
    public void handle(DBError dbError) {
        		/* 通知日志系统进行有关数据库错误的处理 */       
    }
    public void handle(CommError commError) {
		/* 通知日志系统进行有关通信错误的处理 */       
    }
}

 


5、 结论

本文从另一个方面,设计模式的适用性,探讨了在进行程序重构时该如何选择重构的目标,以及如何对现有的设计模式进行改进使之符合我们的目标。本文的主要目的是想说明,在进行设计模式的选择时,不仅要关注设计模式是解决什么问题的,还要关注使用该设计模式解决问题时会受到什么约束和限制。这样就可以帮助你更好地理解问题,做出合理的取舍,或者你可以根据自己的需求对已有的设计模式进行修改使之满足你的要求,如果你这样做了,你就很可能创造出了一种新的设计模式。

<!-- CMA ID: 53043 --><!-- Site ID: 10 --><!-- XSLT stylesheet used to transform this file: dw-article-6.0-beta.xsl -->

 

分享到:
评论

相关推荐

    设计模式C++学习之访问者模式(Visitor)

    访问者模式(Visitor)是一种行为设计模式,它允许在不修改对象结构的前提下向对象结构中的元素添加新的操作。这种模式将算法与数据结构分离,使得算法可以独立于数据结构进行变化,增强了系统的可扩展性。 在C++中...

    设计模式系列之visitor

    "设计模式系列之visitor"是一个关于软件设计模式的讨论,特别是关注于“访问者”(Visitor)模式。这个模式是GOF(Gamma, Helm, Johnson, Vlissides)在他们的经典著作《设计模式:可复用面向对象软件的基础》中提出...

    C#设计模式之Visitor

    **C#设计模式之Visitor** **一、设计模式概述** 设计模式是软件开发中的经验总结,它提供了解决常见问题的可复用解决方案。在C#编程中,设计模式可以帮助我们编写更灵活、可扩展和易于维护的代码。"Visitor"(访问...

    设计模式-Java语言中的应用(pdf)

    《设计模式——Java语言中的应用》是一本专为Java开发者深入理解面向对象设计而编写的经典书籍。设计模式是软件工程领域中经过实践验证的、解决常见问题的有效方案,它们代表了在特定上下文中,针对特定问题的最优...

    设计模式之访问者模式(Visitor)

    访问者模式是一种行为设计模式,它使你可以在不修改对象结构的情况下,为对象添加新的操作。这种模式的核心在于将数据结构与对这些数据的操作解耦,使得增加新的操作变得容易,同时也允许独立地改变元素类和访问者类...

    24种设计模式以及混合设计模式

    在本文中,我们将深入探讨24种设计模式,并结合混合设计模式的概念,以及它们在实际项目中的应用案例。 首先,设计模式分为三大类:创建型、结构型和行为型。创建型设计模式关注对象的创建过程,如单例模式...

    Java设计模式之禅

    《Java设计模式之禅》是一本深入浅出讲解设计模式的书籍,书中不仅包含23种经典设计模式的案例,还详细介绍了设计模式背后的思想和原则,适合初学者以及对设计模式有一定了解的程序员阅读。本书旨在帮助读者理解如何...

    C#面向对象设计模式纵横谈(24):(行为型模式) Visitor 访问者模式

    - **《设计模式:可复用面向对象软件的基础》**(GoF):经典的设计模式指南,详细介绍了23种设计模式,包括Visitor模式。 - **《面向对象分析与设计》**(Grady Booch):提供了面向对象设计的基本原则和方法论,有...

    设计模式之访问者模式(Visitor Pattern)

    **访问者模式(Visitor Pattern)**是一种行为设计模式,它提供了一种在不修改对象结构的情况下增加新操作的方法。这种模式的主要思想是将数据结构与算法分离,使得算法可以在不改变对象结构的情况下独立变化。 在...

    基于visitor模式和访问者模式的表达式树_求值引擎

    2. **访问者模式**:这是与Visitor模式相关的另一种设计模式,但在这里,我们可能是指在表达式树的节点上应用访问者模式。访问者模式使得可以在不修改节点类的情况下,增加对节点的新功能。在本项目中,可能是通过在...

    二十三种设计模式【PDF版】

    设计模式之 Visitor(访问者) 访问者在进行访问时,完成一系列实质性操作,而且还可以扩展. 设计模式引言 设计面向对象软件比较困难,而设计可复用的面向对象软件就更加困难。你必须找到相关的对象,以适当的粒度将...

    C++设计新思维:泛型编程与设计模式之应用

    《C++设计新思维:泛型编程与设计模式之应用》这本书深入探讨了C++语言在泛型编程和设计模式中的应用,对于理解和提升C++编程能力有着重要的指导价值。以下将围绕这些主题展开详细讨论。 一、泛型编程 泛型编程是...

    设计模式-访问者(Visitor)模式详解和应用.pdf

    ### 设计模式-访问者(Visitor)模式详解和应用 #### 一、访问者模式简介 访问者模式(Visitor Pattern)是一种行为型设计模式,它允许我们向一组已存在的类添加新的行为,而无需修改这些类。这种模式的核心思想是在...

    设计模式那点事

    在这里,我们将深入探讨设计模式的核心概念、分类以及它们在实际开发中的应用。 首先,设计模式基于面向对象编程思想,其核心目标是提高代码的可复用性、可读性和可维护性。设计模式不直接提供代码实现,而是描述在...

    23种面向对象设计模式

    文档中的“23种设计模式学习笔记.doc”可能包含了对这23种模式的详细解释和实例,而“设计模式之我爱我家.doc”可能从一个更生活化的角度来阐述设计模式的概念。“软件23种设计模式,超级经典的.pdf”可能是对这些...

    《Java设计模式》课后答案-刘伟.rar

    行为型设计模式如策略(Strategy)、模板方法(Template Method)、观察者(Observer)、命令(Command)、迭代器(Iterator)、访问者(Visitor)、状态(State)、职责链(Chain of Responsibility)、解释器...

    设计模式-访问者模式(Visitor)

    访问者模式(Visitor)是一种行为设计模式,它允许在不修改对象结构的前提下向对象结构中的元素添加新的操作。这种模式的核心思想是分离了算法和对象结构,使得算法可以在不改变对象结构的情况下独立变化。 访问者...

    设计模式:Java语言中的应用.zip

    在Java语言中,设计模式的应用广泛且至关重要,因为它们能帮助开发者遵循良好的编程原则,如开闭原则、单一职责原则、依赖倒置原则等。 首先,我们来看一些基本的设计模式类型: 1. 创建型模式:这类模式主要处理...

    《java设计模式》课后习题模拟试题解答——刘伟.zip

    本资料“《java设计模式》课后习题模拟试题解答——刘伟.zip”主要涵盖了Java设计模式的学习与应用,特别是针对刘伟教授的相关课程的课后习题及模拟试题的解答。 设计模式分为三大类:创建型、结构型和行为型模式。...

Global site tag (gtag.js) - Google Analytics