C++ 访问者设计模式和多层类层次结构
我与相关访客共有五节课:C++ 访问者设计模式和多层类层次结构,c++,design-patterns,visitor-pattern,C++,Design Patterns,Visitor Pattern,我与相关访客共有五节课: struct访问者 { virtual~Visitor()=默认值; 虚拟无效访问(A&{} 虚拟无效访问(B&{} 虚拟无效访问(C&{} 虚拟无效访问(D&{} 虚拟无效访问(E&{} }; 结构A { virtual~A()=默认值; 虚拟无效接受(访问者&v){v.visit(*this);} }; 结构B:A{void accept(Visitor&v)重写{v.visit(*this);}; 结构C:A{void accept(Visitor&v)重写{v.
struct访问者
{
virtual~Visitor()=默认值;
虚拟无效访问(A&{}
虚拟无效访问(B&{}
虚拟无效访问(C&{}
虚拟无效访问(D&{}
虚拟无效访问(E&{}
};
结构A
{
virtual~A()=默认值;
虚拟无效接受(访问者&v){v.visit(*this);}
};
结构B:A{void accept(Visitor&v)重写{v.visit(*this);};
结构C:A{void accept(Visitor&v)重写{v.visit(*this);};
结构D:C{void accept(Visitor&v)重写{v.visit(*this);};
结构E:C{void accept(Visitor&v)重写{v.visit(*this);};
所有实例都将由用户代码在尽可能高的抽象级别上查看,因此它们都将被视为A&
。用户代码需要执行两种类型的操作:
C
C
或其任何子类型(即D
或E
),则打印“我是C”
)struct OperationOne : Visitor
{
void visit( C& ) override { std::cout << "I am C" << std::endl; }
};
问题是:对于第二个操作,假设我们不想重复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; }
};
这样,如果层次结构发生更改,我们将出现编译错误,因为在尝试传播接受的访问者时,编译器将再也找不到基类
这就是说,我们现在可以编写第二个operation visitor,这次我们不需要为D
和E
复制打印代码:
struct OperationTwo : Visitor
{
void visit(C&) override { std::cout << "I am C" << std::endl; }
}
但是等待:OperationOne
和OperationTwo
代码完全相同!这意味着,通过改变第二次运营的基础设施,我们基本上打破了第一次运营。事实上,现在还有操作一个将打印三次字符串“I am C”
要使操作一个
和操作二个
看起来单独工作,可以做些什么?我是否需要将访问者设计模式与其他设计模式相结合,或者我是否完全不需要使用访问者?您可以使用以下内容作为访问者,这些内容将以重载解析方式发送:
模板
结构重载访问者:访问者
{
F;
无效访问(A&A)覆盖{f(A);}
无效访问(B&B)覆盖{f(B);}
无效访问(C&C)覆盖{f(C);}
无效访问(D&D)覆盖{f(D);}
无效访问(E&E)覆盖{f(E);}
};
然后
struct IAmAC
{
void operator()( C& ) { std::cout << "I am C" << std::endl; }
void operator()( A& ) {} // Fallback
};
using OperationTwo = OverloadVisitor<IAmAC>;
struct IAmAC
{
void operator()(C&){std::cout第一个操作在设计方面没有多大意义。为什么你想知道任何东西的确切类型?它会影响LSP。(我不是在谈论诸如序列化之类的基础类型代码,而是abiut应用程序级逻辑)@n.m对我来说,这是有道理的。我们使用这些访问者作为场景图的遍历器,用于各种操作。因此,我知道这两种情况都是可以想象的。(因此,我对这个问题的兴趣可能会得到回报。);-)这可能是有意义的…就目前而言。这种设计通常很脆弱,在层次结构中没有或不太重要的变化的情况下会崩溃。如果你决定将C分成两个类,或者让它成为一个带有具体子类的抽象类,或者诸如此类,那么你的设计应该继续有效。如果它对你有效,当然欢迎你使用它。@n、 m我部分同意。抽象基类的技巧确实是我曾经用作解决方案的东西。另一方面:场景图上的必要操作会随着时间的推移而演变(随着需求和用例数量的增加)很难预见每一个特定的需求,并在数据设计中考虑它。如果操作导致矛盾的要求,甚至是不可能的。
struct OperationTwo : Visitor
{
void visit(C&) override { std::cout << "I am C" << std::endl; }
}
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 );
}
}
struct IAmAC
{
void operator()( C& ) { std::cout << "I am C" << std::endl; }
void operator()( A& ) {} // Fallback
};
using OperationTwo = OverloadVisitor<IAmAC>;