草庐IT

C++:这个模式有名字吗,可以改进吗?

coder 2024-02-05 原文

动机

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

我还想要一个 SpecialTree类(class)。 SpecialTree应该扩展 Tree 的接口(interface)并且可以在任何地方使用 Tree是。

在幕后,TreeSpecialTree可能有完全不同的实现。例如,我可能会使用图书馆的 GraphA类来实现 Tree ,所以 Tree::NodeGraphA::Node 的薄包装器或 typedef .另一方面,SpecialTree可能会根据 GraphB 来实现对象,和一个 Tree::Node包装 GraphB::Node .

稍后我将拥有处理树的函数,例如深度优先搜索函数。此函数应同时接受 TreeSpecialTree对象互换。

模式

我将使用模板化接口(interface)类来定义树和特殊树的接口(interface)。模板参数将是实现类。例如:

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) .这也抛出了严格定义的接口(interface),该接口(interface)描述了 Tree 的具体方法。或其后代应该有。自SpecialTree应该总是表现得像Tree ,但提供一些额外的方法,我喜欢使用接口(interface)。

而不是 TreeInterface模板参数是实现,我可以让它采用一个“表示”类来定义 Node 是什么看起来像(它还必须定义 Arc 看起来像什么,等等)。但是由于我可能需要为每个实现使用其中之一,所以我想我希望将它与实现类本身放在一起。

使用这个模式我有什么收获?大多数情况下,松散的耦合。如果我想更改 Tree 后面的实现, SpecialTree完全不介意,因为它只继承接口(interface)。

问题

那么,这个图案有名字吗?我通过存储指向 ContourTreeImplementation 的指针来使用句柄主体模式在 ContourTree .但是拥有模板化界面的方法呢?这个有名字吗?

有更好的方法吗?看起来我确实重复了很多,写了很多样板代码,但是那些嵌套的 Node类(class)给我带来麻烦。如果Tree::NodeSpecialTree::Node有相当相似的实现,我可以定义一个 NodeInterface Node 的界面在 TreeInterface ,并覆盖 Tree 中节点类的实现和 SpecialTree .但事实上,我不能保证这是真的。 Tree::Node可以包装一个 GraphA::Node , 和 SpecialTree::Node可以包装一个整数。所以这个方法不太行得通,但似乎还有改进的余地。有什么想法吗?

最佳答案

看起来像是 Curiously Recurring Template Pattern 的混合物和 Pimpl idiom .

在CRTP中,我们导出Tree来自 TreeInterface<Tree> ;在你的代码中你得到 Tree来自 TreeInterface<TreeImplementation> .所以它也正如@ElliottFrisch 所说:它是 strategy pattern 的应用程序.代码的某些部分关心 Tree符合 TreeInterface ,而某些其他部分关心的事实是它使用了特定的策略 TreeImplementation .

Is there a better way to do this? It does seem that I am repeating myself a lot

好吧,这取决于您的运行时要求是什么。当我查看您的代码时,突然想到的是您正在使用 virtual方法——懒惰!您的类层次结构如下所示:

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调用者传递给 depthFirstSearch实际上符合 TreeInterface .好吧,我认为执行该限制的最 C++11ish 方式是使用 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 .你同样可以让它返回 truefalse然后点击 static_assertdepthFirstSearch() .

无论如何,这就是我处理问题的方式。请注意,我的整个帖子都是出于摆脱那些低效和困惑的愿望 virtual s — 其他人可能会捕获问题的不同方面并给出完全不同的答案。

关于C++:这个模式有名字吗,可以改进吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21630680/

有关C++:这个模式有名字吗,可以改进吗?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  5. ruby - 我可以使用 Ruby 从 CSV 中删除列吗? - 2

    查看Ruby的CSV库的文档,我非常确定这是可能且简单的。我只需要使用Ruby删除CSV文件的前三列,但我没有成功运行它。 最佳答案 csv_table=CSV.read(file_path_in,:headers=>true)csv_table.delete("header_name")csv_table.to_csv#=>ThenewCSVinstringformat检查CSV::Table文档:http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Table.html

  6. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  7. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  8. ruby - 我可以使用 aws-sdk-ruby 在 AWS S3 上使用事务性文件删除/上传吗? - 2

    我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的

  9. ruby - 有人可以帮助解释类创建的 post_initialize 回调吗 (Sandi Metz) - 2

    我正在阅读SandiMetz的POODR,并且遇到了一个我不太了解的编码原则。这是代码:classBicycleattr_reader:size,:chain,:tire_sizedefinitialize(args={})@size=args[:size]||1@chain=args[:chain]||2@tire_size=args[:tire_size]||3post_initialize(args)endendclassMountainBike此代码将为其各自的属性输出1,2,3,4,5。我不明白的是查找方法。当一辆山地自行车被实例化时,因为它没有自己的initialize方法

  10. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

随机推荐