访问者模式 VS 迭代器模式:跨层次结构类访问?

jus*_*rld 3 iterator visitor-pattern

我正在研究访问者模式的优点,并引用设计模式

但是迭代器不能跨具有不同类型元素的对象结构工作。例如,第 295 页定义的 Iterator 接口只能访问 Item 类型的对象:

template <class Item> 
clas  Iterator { // ... Item CurrentItem() const; };
Run Code Online (Sandbox Code Playgroud)

这意味着迭代器可以访问的所有元素都有一个共同的父类 Item。访客没有此限制...

class Visitor {
public:
// ...
void VisitMyType(MyType*);
void VisitYourType(YourType*);
};
Run Code Online (Sandbox Code Playgroud)

MyType 和 YourType 根本不必通过继承关联。

我同意这句话,但我无法找出访问者模式可以探索List其中收集的对象与超类不相关的结构(如 a )的示例。

换句话说,您能给我举一个例子来证明上述特征是正确的吗?

Boe*_*seB 6

首先,您应该知道这些模式的用途。

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

访问者模式用于对元素结构执行操作,而无需更改元素本身的实现。

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

在访问者模式中,您在要访问的每个元素中实现接口 IAcceptor。因此,访问者模式不依赖于超类,而是依赖于接口

public interface IAcceptor
{
    public void Accept(IVisitor visitor);
}
Run Code Online (Sandbox Code Playgroud)

因此,如果您有一个对象列表,您可以对其进行迭代并访问实现 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

要完成这里的代码,如果访问者访问我的或另一个接受器的实现,则将其写入控制台。

public class MyVisitorImplementation : IVisitor
{
        public void Visit(MyAcceptorImplementation item)
        {
            Console.WriteLine("Mine");
        }
        public void Visit(AnotherAcceptorImplementation item)
        {
            Console.WriteLine("Another");
        }
}
Run Code Online (Sandbox Code Playgroud)

有关更多有用的示例和更好的解释,请查看访问者模式迭代器模式

编辑:这里是一个同时使用访问者和迭代器的示例。迭代器只是如何在聚合中移动的逻辑。如果采用分层结构会更有意义。

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
}
Run Code Online (Sandbox Code Playgroud)


Pra*_*tic 5

我知道有两个很好的例子,其中访问者显然比迭代器更可取。

第一个是与一些未知的类成员集交互,特别是在 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
Run Code Online (Sandbox Code Playgroud)

科利鲁

没有明智的方法让迭代器在这里工作。甚至不知道我们的Printer类可能与什么类型交互,只要访问者被accept()编辑并且所有元素都以类似的方式与operator <<流交互即可。

我知道的另一个很好的例子出现在抽象语法树操作中。CPython 和 LLVM 都使用访问者。此处使用访问者可以防止操作某些 AST 节点的代码需要知道如何迭代可能以复杂方式分支的所有各种 AST 节点。LLVM源代码更详细。亮点如下:

/// 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;
Run Code Online (Sandbox Code Playgroud)