我正在研究访客模式优势,并引用Design Patterns:
但是迭代器不能跨越具有不同的对象结构 元素的类型。例如,页面上定义了Iterator接口 295只能访问Item类型的对象:
template <class Item>
clas Iterator { // ... Item CurrentItem() const; };
这意味着迭代器可以访问的所有元素都有一个共同的父类Item。 访客没有此限制......
class Visitor {
public:
// ...
void VisitMyType(MyType*);
void VisitYourType(YourType*);
};
MyType和YourType不必通过继承关联 所有
我同意这个引用,但是我无法弄清楚访问者模式可以探索一个结构(例如List
)的示例,其中收集的对象与超类没有关联。
换句话说,你能告诉我一个上述功能的例子吗?
答案 0 :(得分:2)
首先,您应该知道这些模式的用途。
Iterator模式用于顺序访问聚合而不暴露其底层表示。因此,您可以在迭代器后面隐藏列表或数组或类似的聚合。
访客模式用于对元素结构执行操作,而不更改元素本身的实现。
因此,您可以在两种不同情况下使用模式,而不是彼此的替代方案。
在访问者模式中,您在要访问的每个元素中实现了一个接口IAcceptor。因此,访问者模式不依赖于超类,而是依赖于接口
public interface IAcceptor
{
public void Accept(IVisitor visitor);
}
因此,如果你有一个对象列表,你可以迭代它并访问实现IAcceptor的对象
public VisitorExample()
{
MyVisitorImplementation visitor = new MyVisitorImplementation();
List<object> objects = GetList();
foreach(IAcceptor item in objects)
item.Accept(visitor);
}
public interface IVisitor
{
public void Visit(MyAcceptorImplementation item);
public void Visit(AnotherAcceptorImplementation item);
}
public class MyAcceptorImplementation : IAcceptor
{
//Some Code ...
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
如果要访问我或其他接受者实现的访问者,请在此处填写代码。
public class MyVisitorImplementation : IVisitor
{
public void Visit(MyAcceptorImplementation item)
{
Console.WriteLine("Mine");
}
public void Visit(AnotherAcceptorImplementation item)
{
Console.WriteLine("Another");
}
}
有关更有用的示例和更好的解释,请查看Visitor Pattern和Iterator Pattern
编辑:这是一个使用访问者和迭代器的例子。迭代器只是如何在聚合中移动的逻辑。使用层次结构会更有意义。
public VisitorExample2()
{
MyVisitorImplementation visitor = new MyVisitorImplementation();
List<object> myListToHide = GetList();
//Here you hide that the aggregate is a List<object>
ConcreteIterator i = new ConcreteIterator(myListToHide);
IAcceptor item = i.First();
while(item != null)
{
item.Accept(visitor);
item = i.Next();
}
//... do something with the result
}
答案 1 :(得分:2)
我知道有两个很好的例子,访问者明显优于迭代器。
第一个是与一些未知的类成员进行交互,特别是在C ++中。例如,这是一个打印出其他类的所有成员的访问者。想象一下,你是Printer
的作者,而你不认识的人是Heterogeneous3Tuple
的作者。
#include <iostream>
template<class ElemType1, class ElemType2, class ElemType3>
class Heterogeneous3Tuple
{
public:
Heterogeneous3Tuple(ElemType1 elem1, ElemType2 elem2, ElemType3 elem3)
: elem1_(std::move(elem1)), elem2_(std::move(elem2)), elem3_(std::move(elem3))
{}
template<class Visitor>
void accept(const Visitor& visitor)
{
visitor(elem1_);
visitor(elem2_);
visitor(elem3_);
}
private:
ElemType1 elem1_;
ElemType2 elem2_;
ElemType3 elem3_;
};
class Printer
{
public:
template<class VisitedElemType>
void operator()(const VisitedElemType& visitee) const
{
std::cout << visitee << std::endl;
}
private:
};
int main() {
Heterogeneous3Tuple<char, int, double> h3t('a', 0, 3.14);
Printer p;
h3t.accept(p);
}
a
0
3.14
没有明智的方法让迭代器在这里工作。只要访问者Printer
并且所有元素都以与accept()
和流相似的方式进行交互,我们甚至不知道我们的operator <<
类可能与哪些类型进行交互。
我所知道的另一个好例子显示在抽象语法树操作中。 CPython和LLVM都使用访问者。在这里使用访问者可以防止操纵某些AST节点的代码需要知道如何迭代可能以复杂方式分支的所有各种AST节点。 LLVM source code更详细。这是重点:
/// Instruction visitors are used when you want to perform different actions
/// for different kinds of instructions without having to use lots of casts
/// and a big switch statement (in your code, that is).
///
/// To define your own visitor, inherit from this class, specifying your
/// new type for the 'SubClass' template parameter, and "override" visitXXX
/// functions in your class. I say "override" because this class is defined
/// in terms of statically resolved overloading, not virtual functions.
///
/// For example, here is a visitor that counts the number of malloc
/// instructions processed:
///
/// /// Declare the class. Note that we derive from InstVisitor instantiated
/// /// with _our new subclasses_ type.
/// ///
/// struct CountAllocaVisitor : public InstVisitor<CountAllocaVisitor> {
/// unsigned Count;
/// CountAllocaVisitor() : Count(0) {}
///
/// void visitAllocaInst(AllocaInst &AI) { ++Count; }
/// };
///
/// And this class would be used like this:
/// CountAllocaVisitor CAV;
/// CAV.visit(function);
/// NumAllocas = CAV.Count;