【问题标题】:Visitor Pattern VS Iterator Pattern: visiting across hierarchy class?访问者模式 VS 迭代器模式:跨层次类访问?
【发布时间】:2018-06-18 21:29:34
【问题描述】:

我正在研究访问者模式的优势,并引用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),其中收集的对象与超类无关。

换句话说,你能给我举个例子,上面的特征是正确的吗?

【问题讨论】:

    标签: iterator visitor-pattern


    【解决方案1】:

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

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

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

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

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

    【讨论】:

    • 对不起,我不同意你的回答。访问者和迭代器模式都可以用来“访问”一个对象结构。正如您所说的那样,访问者模式中的所有访问元素都实现了“超级接口”。结果(正如书中所说),被访问的对象没有必要共享一个公共类(但一个公共接口)。我不确定,文本中显示的示例是:为什么不返回实现接口的对象而不是类的对象?简单来说:如果公共接口是IVisitable,那么写IVisitable CurrentItem()
    • 您可以使用迭代器而不是列表来迭代项目,然后使用访问者。访问者使用它访问的对象的数据进行一些计算或对数据进行一些其他操作。迭代器只是在集合/聚合中移动,同时隐藏其实现。但是你确实可以实现一个在接口上工作的迭代器。
    【解决方案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
    

    coliru

    没有明智的方法让迭代器在这里工作。甚至不知道我们的 Printer 类可能会与此交互的类型,只要访问者是 accept()ed 并且元素都以类似的方式与 operator &lt;&lt; 和流交互。

    我知道的另一个很好的例子出现在抽象语法树操作中。 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;
    

    【讨论】:

      猜你喜欢
      • 2014-03-22
      • 1970-01-01
      • 1970-01-01
      • 2022-12-04
      • 1970-01-01
      • 2011-02-20
      • 1970-01-01
      • 2010-11-02
      • 1970-01-01
      相关资源
      最近更新 更多