一道考验你设计能力的C++编程题 (三)

2014-11-24 11:58:28 · 作者: · 浏览: 9
rame();
p1.Frame();
cout << p1;
cout <

//
CPicture pHorCat(p1, p2, false);
cout << pHorCat;
cout <

//
CPicture pVerCat(p1, pHorCat, true);
cout << pVerCat;
cout <

//
pVerCat.Frame();
cout << pVerCat;
cout <

//
pVerCat.Frame();
cout << pVerCat;
cout <

//
pVerCat.UnFrame();
pVerCat.UnFrame();
cout << pVerCat;
cout <

system("pause");

return 0;
}

可以看到使用起来非常方便和友好,运行截图:
\

可以看到使用第二种实现我们只存储了一份字符串图像数据,同时有保留了图像的层次和结构属性,实现时包含了很多设计模式,比如Template, Decorate, Composite, faced等,简单而高效。


最后我们对这2种实现方式作下比较:
方法1的优势是数据完整,修改一个对象时不会影响其他对象,因为每个对象都是数据的单独拷贝。劣势是低效,不能体现对象的结构属性,我们不知道这个对象是加边框的对象还是上下合成的对象。

方法2的优势是高效,数据共享,同时有保留有对象的结构属性。劣势是修改一个对像时会影响其他的对象,因为他们可能是共享同一个对象。实际上,对于基于引用计数的共享对象,还有一种叫做Write Copy(写入时拷贝)的技术,就是如果你要修改一个对象,就自己拷贝一份。同时引用计数技术还有一个风险就是循环引用,比如A引用了B,B也引用了A,这2个对象就永远没法释放了,这也是要谨慎的。

上面完美的解决了我们UnFrame(去边框)的问题,我们正对我们使用基于引用计数的技术来完美的构造字符串图像类层次而洋洋得意,但是好景不长。

一个星期后,客户又找到你提了他的新需求,他想让你的CPicuture类增加一个功能,能返回一个XML格式的字符串来告诉他该对象的构造过程。
比如
+-------+
|Paris |
|in the |
|Spring |
+-------+
返回的XML串是
< CPic_Frame >
Paris in the Spring

+-------+Paris
|Paris |in the
|in the |Spring
|Spring |
+-------+
返回的XML串是
< CPic_HCat >
< CPic_Frame >
Paris in the Spring

Paris in the Spring

+-------+Paris
|Paris |in the
|in the |Spring
|Spring |
+-------+
Paris
in the
Spring
返回的XML串是

< CPic_HCat >
< CPic_Frame >
Paris in the Spring

Paris in the Spring

Paris in the Spring

你不禁抱怨道,该死的客户,上次已经因为要支持UnFrame功能而让我改变了最初的设计,如果没有客户的新需求,开发该是一件多么美好的事情。

但是抱怨归抱怨,客户就是上帝,你还是只能硬这头皮把事情做完。
那现在让我们来考虑如果实现这一功能。

一开始想到的当然是在我们的CPic_Base基类中增加一个接口,比如
String GetStructXMLString();
但是面向对像的设计原则告诉我们,接口不该随便改动,实际上次CPic_Base里为UnFrame而增加的CRefPtr GetUnFrame()接口已经让你觉得很不爽,感觉这个接口和我们的图像对象没有直接关系。

那么我们是否考虑可以重构CPic_Base接口,让它能以插件的形式实现各种功能,也就是说我们的类层次这里是固定的,但是方法却可以一直增加而不影响原有的代码。

这时我们想到了Visitor模式,它基本上是为我们这类需求而量身定做的。
对于Visitor模式的架构,基本上是固定的,定义个IPic_Visitor
class IPic_Visitor
{
public:
virtual void VisitPicString(CPic_String& pic) {};
virtual void VisitPicFrame(CPic_Frame& pic) {} ;
virtual void VisitPicVCat(CPic_VCat& pic) {};
virtual void VisitPicHCat(CPic_HCat& pic) {};

virtual ~IPic_Visitor() {}
};


在我们的CPic_Base基类里增加一个Accept接口virtual void Accept(IPic_Visitor& visitor) = 0;
这样图像对象就可以让各种类型的Visitor访问了,各个图像类的实现也很简单:
void CPic_String::Accept(IPic_Visitor& visitor)
{
visitor.VisitPicString(*this);
}
void CPic_Frame::Accept(IPic_Visitor& visitor)
{
visitor.VisitPicFrame(*this);
}
void CPic_VCat::Accept(IPic_Visitor& visitor)
{
visitor.VisitPicVCat(*this);
}
void CPic_HCat::Accept(IPic_Visitor& visitor)
{
visitor.VisitPicHCat(*this);
}

好了,现在我们用一个新Visitor来改写我们原来的UnFrame功能,
class CUnFrameVisitor: public IPic_Visitor
{
public:
virtual void VisitPicFrame(CPic_Frame& pic);

public:
CRefPtr GetUnFrameResult();

protected:
CRefPtr m_picRet;
};
因为Visitor方法都是没有返回值,参数也是固定的,所以一般都是通过在Visitor里保存成员变量和返回接口来实现返回值的。
这样实现就很简单了:
void CUnFrameVisitor::VisitPicFrame(CPic_Frame& pic)
{
m_picRet = pic.m_pic;
}

CRefPtr CUnFrameVisitor::GetUnFrameResult()
{
return m_picRet;
}
可以看到只有访问 CPic_Frame才有非空的返回值;其他都是用默认的空方法,最终返回的也就空对象。

这样我们在最终暴露的CPicture里实现UnFrame也就很简单了:
bool CPicture::UnFrame()
{
CUnFrameVisitor vistor;
m_pic->Accept(vistor);

CRefPtr pRet = vistor.GetUnFrameResult();
if(!pRet.IsNull())
{
m_pic = pRet;
}

return !pRet.IsNull();
}

接下来我们考虑如何实现客户的要求返回XML串的需求,实际上我们前面的Visitor模式已经为我们准备好了条件,我们只需要新增加一个Visitor www.2cto.com
class CStructXMLVisitor: public IPic_Visitor
{
public:
virtual void VisitPicString(CPic_String& pic);
virtual void VisitPicFrame(CP