在这里,我使用了COMPOSITE模式。对于COMPOSITE模式,可以参考我那篇《重读《设计模式》之学习笔记(五)--我对COMPOSITE模式的理解》。
现在,我们来分析一下上面的这个设计方案。如果说,由于客户需求的改变,视图中每个元素的显示方法变了,那么我就就要更改每个类的Show方法(当然,客户还有把需求改回去的可能,那样我们还得把Show方法给改回去。虽然这样会使程序员痛苦不堪,但是这种可能还是存在的。我想,做开发的人都会遇到类似的情况。本书中第7章就有这样两句话:“软件开发最重要的事实之一:需求总是在变化。”,“在大多数软件项目中最不稳定的东西就是需求。”)。另外一种情况,客户要求视图可以通过打印机打印,那我们就得给每一个类就要加上一个Print方法。或者,客户要求可以统计视图中每种元素的个数,那么我们还得给类CTView添加一个统计的方法。每次更改,所有使用这些类的代码都要重新编译。
从上面的分析可以看出,我们这个设计是不成功的。在这个时候我们就应该使用VISITOR模式。VISITOR模式的好处就是可以在不改变现有类层次结构的情况下向其中增加新方法。听起来似乎很神奇,下面我就用代码来说明:
首先,创建一个CVisitor的抽象类:
{
public :
virtual ~ CVisitor();
virtual void VisitRectangle(CTRectangle * pRectangle) = 0 ;
virtual void VisitSquare(CTSquare * pSquare) = 0 ;
virtual void VisitCircle(CTCircle * pCircle) = 0 ;
virtual void VisitText(CTText * pText) = 0 ;
virtual void VisitView(CTView * pView) = 0 ;
};
我们要通过该类的子类来访问视图中的各个元素。
下面是重新设计后视图及其各个元素的代码:
{
public :
virtual ~ CContext();
virtual void Accept(CVisitor & v) = 0 ;
};
class CTRectangle : public CContext
{
public :
void Accept(CVisitor & v) { v.VisitRectangle( this ); };
};
class CTSquare : public CContext
{
public :
void Accept(CVisitor & v) { v.VisitSquare( this ); };
};
class CTCircle : public CContext
{
public :
void Accept(CVisitor & v) { v.VisitCircle( this ); };
};
class CTText : public CContext
{
public :
void Accept(CVisitor & v) { v.VisitText( this ); };
};
class CTView : public CContext
{
public :
~ CTView();
void Accept(CVisitor & v);
void Add(CContext * pContext);
private :
vector < CContext *> m_vContext;
};
CTView:: ~ CTView()
{
while ( ! m_vContext.empty())
{
CContext * pContext = (CContext * )m_vContext.back();
m_vContext.pop_back();
delete pContext;
}
}
void CTView::Accept(CVisitor & v)
{
for (vector < CContext *> ::iterator i = m_vContext.begin(); i != m_vContext.end(); ++ i)
{
( * i) -> Accept(v);
}
v.VisitView( this );
}
void CTView::Add(CContext * pContext)
{
m_vContext.push_back(pContext);
}
下面,我们为上面的类添加一个显示视图中各个元素并且计算各个元素个数的visitor:
{
public :
CShowContextVisitor();
void VisitRectangle(CTRectangle * pRectangle);
void VisitSquare(CTSquare * pSquare);
void VisitCircle(CTCircle * pCircle);
void VisitText(CTText * pText);
void VisitView(CTView * pView);
private :
int m_iRectangleCount;
int m_iSquareCount;
int m_iCircleCount;
int m_iTextCount;
};
CShowContextVisitor::CShowContextVisitor()
: m_iRectangleCount( 0 )
, m_iSquareCount( 0 )
, m_iCircleCount( 0 )
, m_iTextCount( 0 )
{}
// 下面以输出一句话来代替具体的显示
void CShowContextVisitor::VisitRectangle(CTRectangle * pRectangle)
{
cout << " A Rectangle is Showed! " << endl;
m_iRectangleCount ++ ;
}
void CShowContextVisitor::VisitSquare(CTSquare * pSquare)
{
cout << " A Square is Showed! " << endl;
m_iSquareCount ++ ;
}
void CShowContextVisitor::VisitCircle(CTCircle * pCircle)
{
cout << " A Circle is Showed! " << endl;
m_iCircleCount ++ ;
}
void CShowContextVisitor::VisitText(CTText * pText)
{
cout << " A Text is Showed! " << endl;
m_iTextCount ++ ;
}
void CShowContextVisitor::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;
}
我们可以用下面的测试函数来验证我们的设计是否正确:
{
CTView TestView;
CTRectangle * pRectangle = new CTRectangle;
TestView.Add(pRectangle);
CTSquare * pSquare = new CTSquare;
TestView.Add(pSquare);
CTCircle * pCircle = new CTCircle;
TestView.Add(pCircle);
CTText * pText = new CTText;
TestView.Add(pText);
CShowContextVisitor TestVisitor;
TestView.Accept(TestVisitor);
}
当然,输出跟我们期望的一样:
A Rectangle is Showed!
A Square is Showed!
A Circle is Showed!
A Text is Showed!
A View is Showed!
Rectangle count: 1
Square count: 1
Circle count: 1
Text count: 1
如果客户提出其他需求,我们只要添加一个相应的visitor就行了,而不用修改我们设计好的类。就算客户把需求改回去,只要重新使用我们以前写好的visitor的文件就行了。
VISISTOR模式最大的好处就是:很容易增加新的操作,而且能把这些相关的操作应该集中在一起,也就是visitor类里。
但是,VISITOR模式有一个致命的弱点,那就是添加相关的元素类比较困难。比如上面的例子中,如果客户要求可以在视图中添加三角形,那么我们除了要写一个CTTriangle类以外,还得修改抽象类CVisitor和它所有的子类。
所以,我们应该在类的层次结构已经固定而操作需要较多的添加或修改时才选择使用VISITOR模式。
Trackback: http://tb.blog.youkuaiyun.com/TrackBack.aspx?PostId=1009585