3

我有五个班级与相关访客:

struct Visitor
{
    virtual ~Visitor() = default;
    virtual void visit(A&) {}
    virtual void visit(B&) {}
    virtual void visit(C&) {}
    virtual void visit(D&) {}
    virtual void visit(E&) {}
};

struct A
{
    virtual ~A() = default;
    virtual void accept(Visitor& v) { v.visit(*this); }
};
struct B : A { void accept(Visitor& v) override { v.visit(*this); } };
struct C : A { void accept(Visitor& v) override { v.visit(*this); } };
struct D : C { void accept(Visitor& v) override { v.visit(*this); } };
struct E : C { void accept(Visitor& v) override { v.visit(*this); } };

用户代码将以可能的最大抽象级别看到所有实例,因此它们都将被视为A&. 用户代码需要做两种类型的操作:

  1. "I am C"如果实例完全属于类型,则打印C
  2. "I am C"如果实例是类型C或其任何子类型(即Dor E) ,则打印

操作 1 的实现非常简单,几乎可以在基础设施到位的情况下使用访问者设计模式框:

struct OperationOne : Visitor
{
    void visit( C& ) override { std::cout << "I am C" << std::endl; }
};

正如预期的那样,字符串"I am C"将只打印一次:

int main( )
{
    A a; B b; C c; D d; E e;
    std::vector<std::reference_wrapper<A>> vec = { a, b, c, d, e };

    OperationOne operation_one;

    for (A& element : vec)
    {
        element.accept(operation_one);
    }
}

演示

问题是:对于第二个操作,整个基础设施不再工作,假设我们不想重复打印代码Dand E

struct OperationTwo : Visitor
{
    void visit( C& ) override { std::cout << "I am C" << std::endl; }
    void visit( D& ) override { std::cout << "I am C" << std::endl; }
    void visit( E& ) override { std::cout << "I am C" << std::endl; }
};

尽管这会起作用,但如果层次结构发生变化并且D不再是 的子类型C,而是例如 的直接子类型A,则此代码仍然可以编译,但在运行时不会出现预期的行为,这是危险且不可取的。

实现操作 2 的一种解决方案是更改访问者基础结构,以便每个可访问类将接受的访问者传播到其基类:

struct B : A
{
    void accept(Visitor& v) override
    {
        A::accept( v );
        v.visit( *this );
    }
};

这样,如果层次结构发生变化,我们将遇到编译错误,因为在尝试传播接受的访问者时,编译器将不再找到基类。

也就是说,我们现在可以编写第二个操作 visitor,这一次我们不需要复制Dand的打印代码E

struct OperationTwo : Visitor
{
    void visit(C&) override { std::cout << "I am C" << std::endl; }
}

正如预期的那样,该字符串"I am C"将在使用时在用户代码中打印三次OperationTwo

int main()
{
    A a; B b; C c; D d; E e;
    vector< reference_wrapper< A > > vec = { a, b, c, d, e };

    OperationTwo operation_two;

    for ( A& element : vec ) 
    {
        element.accept( operation_two );
    }
}

演示

但是等等:代码是完全一样的OperationOneOperationTwo这意味着通过改变第二次操作的基础设施,我们基本上打破了第一次。事实上,现在也OperationOne将打印三遍字符串"I am C"

可以做些什么来无缝地一起工作OperationOneOperationTwo工作?我需要将访问者设计模式与另一个设计模式结合起来,还是根本不需要使用访问者?

4

1 回答 1

4

您可以将以下内容用作访问者,这些内容将通过重载解析进行调度:

template <typename F>
struct OverloadVisitor : Visitor
{
    F f;

    void visit(A& a) override { f(a); }
    void visit(B& b) override { f(b); }
    void visit(C& c) override { f(c); }
    void visit(D& d) override { f(d); }
    void visit(E& e) override { f(e); }
};

接着

struct IAmAC
{
    void operator()( C& ) { std::cout << "I am C" << std::endl; }
    void operator()( A& ) {} // Fallback
};

using OperationTwo = OverloadVisitor<IAmAC>;

演示

于 2019-09-05T10:07:06.340 回答