本系列文章基于我之前的“抽象数据类型与类层次”系列,专门讲解“抽象容器”及其相关概念。(点击打开链接)
一、容器类
根据上面的类层次图,容器也是一种“Object”。容器是一种能够存储其他物体的物体。下面使用一个容器类来抽象容器这个概念。
<span style="font-size:14px;">#pragma once
#include "Object.h"
#include "OwnerShip.h"
#include "Visitor.h"
#include "Iterator.h"
class Container : public virtual Object, public virtual OwnerShip
{
public:
virtual unsigned int Count() const;
virtual bool IsEmpty() const;
virtual bool IsFull() const;
//virtual std::hash<Object> Hash() const;
virtual void Put(std::ostream &)const;
virtual Iterator & NewIterator() const;
virtual void Purge() = 0;
virtual void Accept(Visitor &) const = 0;
protected:
Container();
unsigned int count;
};
</span>
注:容器类是一种抽象类,并不产生实际的具体对象,故其构造函数可以是“Protercted”。
<span style="font-size:14px;">#include "stdafx.h"
#include "Container.h"
#include "NullIterator.h"
#include <typeinfo>
Container::Container()
: count(0)
{
}
unsigned int Container::Count() const
{
return count;
}
bool Container::IsEmpty() const
{
return Count() == 0;
}
bool Container::IsFull() const
{
return false;
}
void Container::Put(std::ostream & s) const
{
PuttingVisitor visitor(s);
s << typeid(*this).name() << " {";
Accept(visitor);
s << "}";
}
Iterator & Container::NewIterator() const
{
return *new NullIterator();
}
</span>
注:“IsEmpty函数”中使用的不是成员变量来直接判断,是因为考虑到容器的子类对计数或空容器的判断方法可能重载。
二、Visitor类
在上面容器类中,有一个“Accept接口函数”,该函数需要一个Vistitor类引用作为参数。这个接口用到的就是Visitor Pattern(访问者模式)。
<span style="font-size:14px;">class Visitor
{
public:
virtual void Visit(Object &) = 0;
virtual bool IsDone() const { return false; }
};</span>
Visitor类其实非常简单,它只有两个接口函数,其中“Visit函数”接受一个Object引用为参数,对该Object对象进行某些处理。因此,Visitor与Container有一个交互,这个交互过程是这样的:Container通过调用Accept函数接受一个Visitor,即接受一种算法或处理方式,然后遍历Container的各个元素,将元素逐个传递给Visitor,对他们逐个进行Visit操作(算法)。这其实也是一种将算法与待处理对象分离的设计方法。
<span style="font-size:14px;">void SomeContainer::Accept (Visitor& visitor) const
for each Object i in this container
if (visitor.IsDone ())
return;
visitor.Visit (i);</span>
<span style="font-size:14px;"><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> Visitor类的另一个函数“IsDone”可以作为“Accept函数”中的循环中断条件。</span></span>
<span style="font-size:14px;"><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> 下面再展示几个具体的Visitor类和全部代码:</span></span>
<span style="font-size:14px;">#ifndef VISITOR_H
#define VISITOR_H
#include "Object.h"
#include <ostream>
class Visitor
{
public:
virtual void Visit(Object &) = 0;
virtual bool IsDone() const { return false; }
};
class PuttingVisitor : public Visitor
{
public:
PuttingVisitor(std::ostream & s)
: stream(s), comma(false)
{}
void Visit(Object & object)
{
if (comma)
stream << ", ";
stream << object;
comma = true;
}
private:
std::ostream & stream;
bool comma;
};
class MatchingVisitor : public Visitor
{
public:
MatchingVisitor(Object const & object)
: target(object), found(NULL)
{}
void Visit(Object & object)
{
if (found == NULL && object == target)
found = &object;
}
bool IsDone() const { return found != NULL; }
private:
Object const & target;
Object * found;
};
#endif // VISITOR_H</span>
返回Container的实现代码看“Put函数”,它里面也用到了一种Visitor。实际上,Visitor是对操作(算法)的一种抽象,不针对具体对象,故它可以用于Accept函数,也可用在其他地方,提高代码复用。这就是Visitor Pattern的优势。
三、Visitor类与仿函数
先看“PuttingVisitor”,它的功能其实可以用一个函数指针来代替。设计一个函数指针,它接受一个Object &参数。再看“MatchingVisitor”,它的功能其实也可以用一个函数对象来代替,只需要在这个函数对象构造的时候给它传递一个参数。此外,使用C++11中的std::function(函数对象包装器)和std::bind,可以将这种设计变得更通用化。