访问者模式 VS 迭代器模式:跨层级访问 class?

Visitor Pattern VS Iterator Pattern: visiting across hierarchy class?

我正在研究访问者模式的优势,并引用 Design Patterns:

But an iteratorcan't work across object structures with different types of elements. Forexample, the Iterator interface defined on page 295 can access only objects of type Item:

template <class Item> 
clas  Iterator { // ... Item CurrentItem() const; };

This implies that all elements the iterator can visit have a common parentclass Item. Visitor does not have this restriction...

class Visitor {
public:
// ...
void VisitMyType(MyType*);
void VisitYourType(YourType*);
};

MyType and YourType do not have to be related throughinheritance at all.

我同意这句话,但我想不出一个示例,其中访问者模式可以探索一个结构(如 List),其中收集的对象与超级 class.

换句话说,你能给我举一个满足上述特征的例子吗?

首先,你应该知道这些模式是干什么用的。

迭代器模式用于顺序访问聚合而不暴露其底层表示。因此,您可以将列表或数组或类似的聚合隐藏在迭代器后面。

访问者模式用于在不更改元素本身实现的情况下对元素结构执行操作。

所以你在两种不同的情况下使用这些模式,而不是相互替代。

在访问者模式中,您在要访问的每个元素中实现一个接口 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 and 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
}

我知道有两个很好的例子,其中访问者明显优于迭代器。

首先是与一些未知的 class 成员交互,特别是在 C++ 中。例如,这是一个打印出其他 classes 的所有成员的访问者。假设您是 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

coliru

这里没有让迭代器工作的合理方法。甚至不知道我们的 Printer class 可能与此作品交互的类型,只要访问者被 accept()ed 并且所有元素都以类似的方式与 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;