消除VISITOR模式中的循环依赖

2024-02-01 12:48

本文主要是介绍消除VISITOR模式中的循环依赖,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

    在我的那篇《VISITOR模式--《敏捷软件开发》读书笔记(三)》中,我用一个C++的小例子说明了设计模式中的VISITOR模式。在例子代码中,我们可以发现:为了使VISITOR类CVisitor通过编译,它就必须知道它要访问的类(CTRectangle,CTSquare,CTCircle, CTText ,CTView )的定义;而这些被访问的类要通过编译,它们必须知道类CVisitor的定义。这样就形成了循环依赖。如下面的类图(带箭头的虚线表示依赖关系): 

    可以看到,由于循环依赖,visitor类和被访问的类之间的依赖关系都是双向的,这张类图看上去跟蜘蛛网差不多。
    虽然我们可以用前置声明来解决编译问题,但是这样的设计会给代码维护带来非常大的麻烦!下面,还用原来的例子,来设计一个消除掉循环依赖的VISITOR模式。
    首先,定义一个VISTOR的基类:

class  CVisitor
{
public :
    
virtual   ~ CVisitor() {}
};

    实际上,这个VISITOR的基类什么都不做,它只是具体类型信息的载体。虽然这样,类CVisitor却非常重要,因为它为VISITOR类提供了RTTI(Run-Time Type Identification)能力。我们可以用dynamic_cast来把CVisitor的指针转换为我们想要的具体的VISITOR类对象的指针。
    然后,针对VISITOR类要访问的每一个类,定义一个小型的VISITOR类:

class  CRectangleVisitor
{
public :
    
virtual   void  VisitRectangle(CTRectangle * =   0 ;
};

class  CSquareVisitor
{
public :
    
virtual   void  VisitSquare(CTSquare * =   0 ;
};

class  CCircleVisitor
{
public :
    
virtual   void  VisitCircle(CTCircle * =   0 ;
};

class  CTextVisitor
{
public :
    
virtual   void  VisitText(CTText * =   0 ;
};

class  CViewVisitor
{
public :
    
virtual   void  VisitView(CTView * =   0 ;
};

    这些小型的抽象VISITOR类只定义了访问的接口函数,由具体的VISITOR类来实现这些函数。
    现在,来修改被访问的类:

class  CContext
{
public :
    
virtual   ~ CContext() {}

    
virtual   void  Accept(CVisitor &  v)  =   0 ;
};

class  CTRectangle :  public  CContext
{
public :
    
void  Accept(CVisitor &  v) 
    {
        
if (CRectangleVisitor  * pVisitor  =  dynamic_cast < CRectangleVisitor *> ( & v))
            pVisitor
-> VisitRectangle( this ); 
    }
};

class  CTSquare :  public  CContext
{
public :
    
void  Accept(CVisitor &  v) 
    {
        
if (CSquareVisitor  * pVisitor  =  dynamic_cast < CSquareVisitor *> ( & v))
            pVisitor
-> VisitSquare( this ); 
    }
};

class  CTCircle :  public  CContext
{
public :
    
void  Accept(CVisitor &  v) 
    {
        
if (CCircleVisitor  * pVisitor  =  dynamic_cast < CCircleVisitor *> ( & v))
            pVisitor
-> VisitCircle( this ); 
    }
};

class  CTText :  public  CContext
{
public :
    
void  Accept(CVisitor &  v) 
    {
        
if (CTextVisitor  * pVisitor  =  dynamic_cast < CTextVisitor *> ( & v))
            pVisitor
-> VisitText( this ); 
    }
};

class  CTView :  public  CContext
{
public :
    
~ CTView()
    {
        
while ( ! m_vContext.empty())
        {
            CContext 
* pContext  =  (CContext * )m_vContext.back();
            m_vContext.pop_back();

            delete pContext;
        }
    }

    
void  Accept(CVisitor &  v)
    {
        
for (vector < CContext *> ::iterator i  =  m_vContext.begin(); i  !=  m_vContext.end();  ++ i)
        {
            (
* i) -> Accept(v);
        }
        
        
if (CViewVisitor  * pVisitor  =  dynamic_cast < CViewVisitor *> ( & v))
            pVisitor
-> VisitView( this );
    }

    
void  Add(CContext  * pContext)
    {
        m_vContext.push_back(pContext);
    }

private :
    vector
< CContext *>  m_vContext;
};

    上面的代码跟原来的不同之处就是:每个Accept方法里面多了一个if语句。在这个if语句中,通过dynamic_cast将传入的参数visitor转换成我们需要的visitor,然后再调用具体的访问函数。
    下面,跟《VISITOR模式--《敏捷软件开发》读书笔记(三)》一样,我们为上面的类添加一个显示视图中各个元素并且计算各个元素个数的visitor:

class  CShowContextVisitor :
    
public  CVisitor,
    
public  CRectangleVisitor,
    
public  CSquareVisitor,
    
public  CCircleVisitor,
    
public  CTextVisitor,
    
public  CViewVisitor
{
public :
    CShowContextVisitor()
        : m_iRectangleCount(0),
          m_iSquareCount(0),
          m_iCircleCount(0),
          m_iTextCount(0)
    {}


    
void  VisitRectangle(CTRectangle  * pRectangle)
    { 
        cout 
<<   " A Rectangle is Showed! "   <<  endl; 
        m_iRectangleCount
++ ;
    }

    
void  VisitSquare(CTSquare  * pSquare)
    { 
        cout 
<<   " A Square is Showed! "   <<  endl;
        m_iSquareCount
++ ;
    }

    
void  VisitCircle(CTCircle  * pircle)
    {
        cout 
<<   " A Circle is Showed! "   <<  endl;
        m_iCircleCount
++ ;
    }

    
void  VisitText(CTText  * pText)
    {
        cout 
<<   " A Text is Showed! "   <<  endl;
        m_iTextCount
++ ;
    }

    
void  VisitView(CTView  * pView)
    {
        cout 
<<   " A View is Showed! "   <<  endl;
        cout 
<<   " Rectangle count:  "   <<  m_iRectangleCount  <<  endl;
        cout 
<<   " Square count:  "   <<  m_iSquareCount  <<  endl;
        cout 
<<   " Circle count:  "   <<  m_iCircleCount  <<  endl;
        cout 
<<   " Text count:  "   <<  m_iTextCount  <<  endl;
    }

private :
    
int  m_iRectangleCount;
    
int  m_iSquareCount;
    
int  m_iCircleCount;
    
int  m_iTextCount;
};

    从上面的代码可以看出,这个类CShowContextVisitor跟原来那篇文章中的实现没有区别,唯一的区别就是:它是从VISITOR的基类CVisitor和那些小型的抽象VISITOR类继承的。这样就可以保证在Accept函数中用dynamic_cast可以动态转换为我们需要的具体VISITOR类,从而调用相应的访问函数。
    下面是这个新设计方案的类图:

    从图中可以看出,原来的循环依赖已经被消除!
    我们可以用《VISITOR模式--《敏捷软件开发》读书笔记(三)》中一样的测试函数来对这个新设计的方案进行测试,当然结果也跟那篇文章一样,都是正确的。
    古人云:有得必有失。这里要说明的是,新的设计方案虽然消除了循环依赖,但是却引入了dynamic_cast。而dynamic_cast在运行期是需要一些时间成本来进行动态类型转换的。如果你的程序对效率要求比较高,那你就不得不用原来的带有循环依赖性的VISITOR模式。

这篇关于消除VISITOR模式中的循环依赖的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/667337

相关文章

gradle第三方Jar包依赖统一管理方式

《gradle第三方Jar包依赖统一管理方式》:本文主要介绍gradle第三方Jar包依赖统一管理方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录背景实现1.顶层模块build.gradle添加依赖管理插件2.顶层模块build.gradle添加所有管理依赖包

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Spring 中的循环引用问题解决方法

《Spring中的循环引用问题解决方法》:本文主要介绍Spring中的循环引用问题解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录什么是循环引用?循环依赖三级缓存解决循环依赖二级缓存三级缓存本章来聊聊Spring 中的循环引用问题该如何解决。这里聊

Nginx location匹配模式与规则详解

《Nginxlocation匹配模式与规则详解》:本文主要介绍Nginxlocation匹配模式与规则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、环境二、匹配模式1. 精准模式2. 前缀模式(不继续匹配正则)3. 前缀模式(继续匹配正则)4. 正则模式(大

Maven如何手动安装依赖到本地仓库

《Maven如何手动安装依赖到本地仓库》:本文主要介绍Maven如何手动安装依赖到本地仓库问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、下载依赖二、安装 JAR 文件到本地仓库三、验证安装四、在项目中使用该依赖1、注意事项2、额外提示总结一、下载依赖登

C# foreach 循环中获取索引的实现方式

《C#foreach循环中获取索引的实现方式》:本文主要介绍C#foreach循环中获取索引的实现方式,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、手动维护索引变量二、LINQ Select + 元组解构三、扩展方法封装索引四、使用 for 循环替代

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

Python如何自动生成环境依赖包requirements

《Python如何自动生成环境依赖包requirements》:本文主要介绍Python如何自动生成环境依赖包requirements问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录生成当前 python 环境 安装的所有依赖包1、命令2、常见问题只生成当前 项目 的所有依赖包1、

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La