我有五个带访问者的类:
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&
。用户代码需要执行两种类型的操作:
- 如果实例恰好是类型
C
,则打印"I am C"
- 如果实例是类型
C
或任何其子类型(即D
或E
),则打印"I am C"
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);
}
}
问题是:对于第二个操作,整个基础架构都不再起作用了,假设我们不想再次重复
D
和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 );
}
};
如果继承层次结构发生变化,编译器在尝试传播已接受的访问者时将无法找到基类,从而导致编译错误。
话虽如此,我们现在可以编写第二个操作访问者,这次我们不需要为D
和E
再次复制打印代码:
struct OperationTwo : Visitor
{
void visit(C&) override { std::cout << "I am C" << std::endl; }
}
当使用OperationTwo
时,用户代码中预计会打印三次字符串"I am C"
:
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 );
}
}
等等: OperationOne
和 OperationTwo
的代码完全相同!这意味着通过更改第二个操作的基础设施,我们基本上破坏了第一个操作。实际上,现在也会有 OperationOne
打印三次字符串 "I am C"
。
为了使 OperationOne
和 OperationTwo
无缝地共同工作,可以采取什么措施?我需要将访问者设计模式与另一种设计模式结合使用,还是根本不需要使用访问者?