C ++:这个模式是否有名称,是否可以改进?

时间:2014-02-07 14:49:01

标签: c++ oop templates design-patterns inheritance

动机

假设我正在写一个Tree课程。我将用Tree::Node类表示树的节点。类的方法可能返回Tree::Node个对象并将它们作为参数,例如获取节点父节点的方法:Node getParent(Node)

我还想要一个SpecialTree课程。 SpecialTree应该扩展Tree的界面,并且可以在Tree的任何位置使用。

在幕后,TreeSpecialTree可能会有完全不同的实现。例如,我可能使用库的GraphA类来实现Tree,因此Tree::NodeGraphA::Node的瘦包装或typedef。另一方面,SpecialTree可以用GraphB对象实现,而Tree::Node包含GraphB::Node

我稍后会有处理树的函数,比如深度优先搜索函数。此函数应该可以互换地接受TreeSpecialTree个对象。

模式

我将使用模板化接口类来定义树和特殊树的接口。 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;
};

SpecialTreeSpecialTreeImplementation类似地定义为TreeTreeImplementation

我的深度优先搜索功能可能如下所示:

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类给我带来了麻烦。如果ContourTreeNode具有相当类似的实现,我可以在Tree::Node中为SpecialTree::Node定义NodeInterface接口,并覆盖节点类的实现NodeTreeInterface。但实际上,我不能保证这是真的。 Tree可以包装SpecialTreeTree::Node可以包装整数。所以这种方法不太合适,但似乎仍有改进的余地。有什么想法吗?

1 个答案:

答案 0 :(得分:2)

看起来像是Curiously Recurring Template PatternPimpl 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。您也可以返回falsetrue,然后点击false中的static_assert

无论如何,这就是我解决问题的方法。请注意,我的整个帖子都是出于摆脱那些效率低下且令人困惑的问题的动机{ - 1}} - 其他人可能会抓住问题的不同方面并给出完全不同的答案。