假设我正在写一个Tree
课程。我将用Tree::Node
类表示树的节点。类的方法可能返回Tree::Node
个对象并将它们作为参数,例如获取节点父节点的方法:Node getParent(Node)
。
我还想要一个SpecialTree
课程。 SpecialTree
应该扩展Tree
的界面,并且可以在Tree
的任何位置使用。
在幕后,Tree
和SpecialTree
可能会有完全不同的实现。例如,我可能使用库的GraphA
类来实现Tree
,因此Tree::Node
是GraphA::Node
的瘦包装或typedef。另一方面,SpecialTree
可以用GraphB
对象实现,而Tree::Node
包含GraphB::Node
。
我稍后会有处理树的函数,比如深度优先搜索函数。此函数应该可以互换地接受Tree
和SpecialTree
个对象。
我将使用模板化接口类来定义树和特殊树的接口。 template参数将是实现类。例如:
template <typename Implementation>
class TreeInterface
{
public:
typedef typename Implementation::Node Node;
virtual Node addNode() = 0;
virtual Node getParent(Node) = 0;
};
class TreeImplementation
{
GraphA graph;
public:
typedef GraphA::Node Node;
Node addNode() { return graph.addNode(); }
Node getParent() { // ...return the parent... }
};
class Tree : public TreeInterface<TreeImplementation>
{
TreeImplementation* impl;
public:
Tree() : impl(new TreeImplementation);
~Tree() { delete impl; }
virtual Node addNode() { return impl->addNode(); }
virtual Node getParent() { return impl->getParent(); }
};
然后我可以从SpecialTreeInterface
TreeInterface
template <typename Implementation>
class SpecialTreeInterface : public TreeInterface<Implementation>
{
virtual void specialTreeFunction() = 0;
};
将SpecialTree
和SpecialTreeImplementation
类似地定义为Tree
和TreeImplementation
。
我的深度优先搜索功能可能如下所示:
template <typename T>
void depthFirstSearch(TreeInterface<T>& tree);
由于SpecialTree
来自TreeInterface
,因此这适用于Tree
个对象和SpecialTree
个对象。
另一种方法是更多地依赖模板,以便SpecialTree
根本不是类型层次结构中TreeInterface
的后代。在这种情况下,我的DFS功能看起来像template <typename T> depthFirstSearch(T& tree)
。这也抛出了严格定义的接口,准确描述了Tree
或其后代应该具有的方法。由于SpecialTree
总是就像Tree
,但提供了一些额外的方法,我喜欢使用界面。
我可以使用一个“表示”类来定义TreeInterface
的外观(它还必须定义{{1}看起来像,等等)。但是因为我可能需要其中一个用于每个实现,我想我想将它与实现类本身保持在一起。
使用此模式可以获得什么?大多数情况下,耦合更松散。如果我想更改Node
背后的实现,Arc
根本不介意,因为它只会继承接口。
那么,这个模式有名字吗?我通过在Tree
中存储指向SpecialTree
的指针来使用句柄体模式。但是如何使用模板化界面呢?这有名字吗?
有更好的方法吗?看起来我似乎在重复自己,编写了很多样板代码,但那些嵌套的ContourTreeImplementation
类给我带来了麻烦。如果ContourTree
和Node
具有相当类似的实现,我可以在Tree::Node
中为SpecialTree::Node
定义NodeInterface
接口,并覆盖节点类的实现Node
和TreeInterface
。但实际上,我不能保证这是真的。 Tree
可以包装SpecialTree
,Tree::Node
可以包装整数。所以这种方法不太合适,但似乎仍有改进的余地。有什么想法吗?
答案 0 :(得分:2)
看起来像是Curiously Recurring Template Pattern和Pimpl idiom的混合物。
在CRTP中,我们从Tree
派生TreeInterface<Tree>
;在您的代码中,您从Tree
派生TreeInterface<TreeImplementation>
。所以它也像@ElliottFrisch所说的那样:它是strategy pattern的应用程序。代码的某些部分关注Tree
符合TreeInterface
,而某些其他部分则关注它使用特定策略 TreeImplementation
的事实。
有更好的方法吗?看起来我似乎在重复自己很多
嗯,这取决于您的运行时要求。当我看到你的代码时,跳出来的东西是你正在使用virtual
方法 - slooooow!您的类层次结构如下所示:
Tree is a child of
TreeInterface<TreeImplementation>
SpecialTree is a child of
TreeInterface<SpecialTreeImplementation>
请注意,TreeInterface<X>::addNode()
恰好是virtual
的事实绝对不会影响 TreeInterface<Y>::addNode()
是否是虚拟的!因此,使这些方法virtual
不会使我们获得任何运行时多态性;我不能编写一个带有TreeInterfaceBase
任意实例的函数,因为我们没有得到一个TreeInterfaceBase
。我们所拥有的只是一包不相关的基类TreeInterface<T>
。
那么,为什么存在这些virtual
方法呢?啊哈。您正在使用virtual
将派生类中的信息传递回父级:子级可以通过继承“查看”其父级,父级可以通过virtual
“查看”子级。这是通常通过CRTP解决的问题。
所以,如果我们使用CRTP(因此不再需要virtual
的东西),我们就是这样:
template <typename Parent>
struct TreeInterface {
using Node = typename Parent::Node;
Node addNode() { return static_cast<Parent*>(this)->addNode(); }
Node getParent(Node n) const { return static_cast<Parent*>(this)->getParent(n); }
};
struct ATree : public TreeInterface<ATree> {
GraphA graph;
typedef GraphA::Node Node;
Node addNode() { return graph.addNode(); }
Node getParent(Node n) const { // ...return the parent... }
};
struct BTree : public TreeInterface<BTree> {
GraphB graph;
typedef GraphB::Node Node;
Node addNode() { return graph.addNode(); }
Node getParent(Node n) const { // ...return the parent... }
};
template <typename Implementation>
void depthFirstSearch(TreeInterface<Implementation>& tree);
此时有人可能会说我们根本不需要使用丑陋的指针式CRTP,我们可以写一下
struct ATree {
GraphA graph;
typedef GraphA::Node Node;
Node addNode() { return graph.addNode(); }
Node getParent(Node n) const { // ...return the parent... }
};
struct BTree {
GraphB graph;
typedef GraphB::Node Node;
Node addNode() { return graph.addNode(); }
Node getParent(Node n) const { // ...return the parent... }
};
template <typename Tree>
void depthFirstSearch(Tree& tree);
并且我个人同意他们的意见。
好的,你担心那时无法通过类型系统确保调用者传递给T
的{{1}}实际上符合depthFirstSearch
。好吧,我认为执行该限制的最多C ++ 11-ish方式是使用TreeInterface
。例如:
static_assert
请注意,如果template<typename Tree>
constexpr bool conforms_to_TreeInterface() {
using Node = typename Tree::Node; // we'd better have a Node typedef
static_assert(std::is_same<decltype(std::declval<Tree>().addNode()), Node>::value, "addNode() has the wrong type");
static_assert(std::is_same<decltype(std::declval<Tree>().getParent(std::declval<Node>())), Node>::value, "getParent() has the wrong type");
return true;
}
template <typename T>
void depthFirstSearch(T& tree)
{
static_assert(conforms_to_TreeInterface<T>(), "T must conform to our defined TreeInterface");
...
}
不符合,我的conforms_to_TreeInterface<T>()
实际上将是静态断言失败;它永远不会真正返回T
。您也可以返回false
或true
,然后点击false
中的static_assert
。
无论如何,这就是我解决问题的方法。请注意,我的整个帖子都是出于摆脱那些效率低下且令人困惑的问题的动机{ - 1}} - 其他人可能会抓住问题的不同方面并给出完全不同的答案。