访问者模式VS迭代器模式:如何在类的层次结构之间进行访问?

3

我正在学习访问者模式的优势,并引用设计模式

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

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

这意味着迭代器可以访问的所有元素都有一个共同的父类Item。而Visitor没有这个限制...
class Visitor {
public:
// ...
void VisitMyType(MyType*);
void VisitYourType(YourType*);
};

MyType和YourType根本不需要通过继承相关。

我同意这个引用,但我想不出一个例子,展示访问者模式如何探索一个结构(比如一个List),其中收集的对象没有通过超类相关联。

换句话说,请你给我展示一个以上述特征为真的例子好吗?

2个回答

6

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

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

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

因此,您在两种不同的情况下使用这些模式,而不是作为彼此的替代品。

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

为了完成代码,这里需要一个Visitor,它可以在访问我的或其他Acceptor的实现时将内容写入控制台。
public class MyVisitorImplementation : IVisitor
{
        public void Visit(MyAcceptorImplementation item)
        {
            Console.WriteLine("Mine");
        }
        public void Visit(AnotherAcceptorImplementation item)
        {
            Console.WriteLine("Another");
        }
}

更多有用的示例和更好的解释,请查看访问者模式迭代器模式
编辑:以下是同时使用访问者和迭代器的示例。迭代器只是指如何遍历聚合对象的逻辑。如果使用分层结构,则更有意义。
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
}

很抱歉,但我不同意你的答案。访问者模式和迭代器模式都可以用于“访问”对象结构。正如你所说,访问者模式中所有被访问的元素都实现了一个“超级接口”。因此(正如书中所述),被访问的对象不需要共享一个公共类(而是共享一个公共接口)。我不确定的是文本中所示的例子:为什么不返回实现接口的对象,而是返回一个类的对象?简单地说:如果公共接口是 IVisitable,那么就写成 IVisitable CurrentItem() - justHelloWorld
你可以使用迭代器而不是列表来遍历项目,然后使用访问者。访问者使用它访问的对象的数据进行一些计算或对数据执行其他操作。迭代器只是在隐藏其实现的情况下移动整个集合/聚合。但是,你确实可以实现一个在接口上工作的迭代器。 - BoeseB

5

我知道两个很好的例子,证明使用visitor比iterator更可取。

第一个例子是在某些未知类成员的交互中,特别是在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

coliru

在这里,没有明智的方法让迭代器工作。即使我们不知道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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接