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

消除VISITOR模式中的循环依赖

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

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

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

classCRectangleVisitor
{
public:
virtualvoidVisitRectangle(CTRectangle*)=0;
};

classCSquareVisitor
{
public:
virtualvoidVisitSquare(CTSquare*)=0;
};

classCCircleVisitor
{
public:
virtualvoidVisitCircle(CTCircle*)=0;
};

classCTextVisitor
{
public:
virtualvoidVisitText(CTText*)=0;
};

classCViewVisitor
{
public:
virtualvoidVisitView(CTView*)=0;
};

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

classCContext
{
public:
virtual~CContext(){}

virtualvoidAccept(CVisitor&v)=0;
};

classCTRectangle:publicCContext
{
public:
voidAccept(CVisitor&v)
{
if(CRectangleVisitor*pVisitor=dynamic_cast<CRectangleVisitor*>(&v))
pVisitor
->VisitRectangle(this);
}
};

classCTSquare:publicCContext
{
public:
voidAccept(CVisitor&v)
{
if(CSquareVisitor*pVisitor=dynamic_cast<CSquareVisitor*>(&v))
pVisitor
->VisitSquare(this);
}
};

classCTCircle:publicCContext
{
public:
voidAccept(CVisitor&v)
{
if(CCircleVisitor*pVisitor=dynamic_cast<CCircleVisitor*>(&v))
pVisitor
->VisitCircle(this);
}
};

classCTText:publicCContext
{
public:
voidAccept(CVisitor&v)
{
if(CTextVisitor*pVisitor=dynamic_cast<CTextVisitor*>(&v))
pVisitor
->VisitText(this);
}
};

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

deletepContext;
}
}

voidAccept(CVisitor&v)
{
for(vector<CContext*>::iteratori=m_vContext.begin();i!=m_vContext.end();++i)
{
(
*i)->Accept(v);
}

if(CViewVisitor*pVisitor=dynamic_cast<CViewVisitor*>(&v))
pVisitor
->VisitView(this);
}

voidAdd(CContext*pContext)
{
m_vContext.push_back(pContext);
}

private:
vector
<CContext*>m_vContext;
};

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

classCShowContextVisitor:
publicCVisitor,
publicCRectangleVisitor,
publicCSquareVisitor,
publicCCircleVisitor,
publicCTextVisitor,
publicCViewVisitor
{
public:
CShowContextVisitor()
: m_iRectangleCount(0),
m_iSquareCount(0),
m_iCircleCount(0),
m_iTextCount(0)
{}


voidVisitRectangle(CTRectangle*pRectangle)
{
cout
<<"ARectangleisShowed!"<<endl;
m_iRectangleCount
++;
}

voidVisitSquare(CTSquare*pSquare)
{
cout
<<"ASquareisShowed!"<<endl;
m_iSquareCount
++;
}

voidVisitCircle(CTCircle*pircle)
{
cout
<<"ACircleisShowed!"<<endl;
m_iCircleCount
++;
}

voidVisitText(CTText*pText)
{
cout
<<"ATextisShowed!"<<endl;
m_iTextCount
++;
}

voidVisitView(CTView*pView)
{
cout
<<"AViewisShowed!"<<endl;
cout
<<"Rectanglecount:"<<m_iRectangleCount<<endl;
cout
<<"Squarecount:"<<m_iSquareCount<<endl;
cout
<<"Circlecount:"<<m_iCircleCount<<endl;
cout
<<"Textcount:"<<m_iTextCount<<endl;
}

private:
intm_iRectangleCount;
intm_iSquareCount;
intm_iCircleCount;
intm_iTextCount;
};

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

消除VISITOR模式中的循环依赖

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

相关文章:

  • 2021-07-16
  • 2021-08-07
  • 2022-01-21
  • 2021-05-29
  • 2021-10-10
  • 2021-12-30
  • 2021-09-05
  • 2021-11-22
猜你喜欢
  • 2021-09-09
  • 2021-09-04
  • 2021-08-30
  • 2021-12-06
  • 2021-12-08
  • 2022-12-23
相关资源
相似解决方案