草庐IT

c++ - 继承包含另一个派生类的 C++ 类

coder 2024-02-25 原文

将下一个 C++ 类作为问题的简化:

struct Car
{
    virtual int get_price() = 0;
};

struct ExpensiveCar: public Car
{
    int get_price( ) {/*..*/ }
    void apply_turbo( ){/*..*/};
};

struct CheapCar: public Car
{
    int get_price( ) {/*..*/}
};

struct CarRetailer
{
    virtual std::vector<Car*> get_cars( ) = 0;
};


struct ExpensiveCarsRetailer : public CarRetailer
{
    virtual std::vector<Car*> get_cars( ) { /*..*/ }
    std::vector<ExpensiveCar*> get_cars_for_exhibitions( );
};

struct CheapCarsRetailer : public CarRetailer
{
    virtual std::vector<Car*> get_cars( ) { /*..*/ }
    std::vector<CheapCar*> get_second_hand_cars( );
};

规则是:昂贵的汽车仅在 ExpensiveCarsRetailers 中出售(与廉价汽车类似)。便宜的车没有涡轮,贵的车不卖二手。

我在这里面临的问题是包含继承类的类的耦合。因此,如果 ExpensiveCarRetailer继承自 CarRetailer , 它需要实现 virtual std::vector<Car*> get_cars( )这实际上是返回 Car* 的 vector ,然而,在内部ExpensiveCarRetailer只创建了 ExpensiveCar 的对象.此外,get_cars_for_exhibitions()不包含在公共(public)接口(interface)中CarRetailer因此,它可以返回 std::vector<ExpensiveCar*>相反。

API 中的混合(Car*ExpensiveCar* 的返回 vector )非常丑陋,用户使用apply_turbo( ) 需要编写的代码也是如此。特定汽车列表的功能ExpesiveCarsRetailer .

ExpensiveCarsRetailer ferrari;

std::vector<Car*> car = ferrari.get_cars();
ExpensiveCar* expensive_car;
for( int i = 0; i < car.size( ); ++i)
{
expensive_car = dynamic_cast<ExpensiveCar*>(car[i]);
expensive_car->apply_turbo();
}

我确信我遗漏了一些在这种情况下有帮助的设计模式,其中类继承树的耦合方式是其中一个继承树的抽象类需要返回一个 vector (或集合、列表、等)的其他继承树上的类。我尽量避免动态转换。

我也想过制作CarRetailer一个模板类,因此:

template<typename T>
    struct CarRetailer
    {
        virtual std::vector<T*> get_cars( ) = 0;
    };

然后制作:

struct ExpensiveCarRetailer: public CarRetailer<ExpensiveCar>
{
...
}

但我认为这行不通,因为例如 CarRetailer也可以开始销售摩托车(类似于汽车的结构)或自行车等(始终应用昂贵/廉价模式),最终需要在 CarRetailer 中定义的模板类的数量将是巨大的。

最佳答案

“也包含继承类的类耦合”的整个概念称为协变。在维基百科上可以找到对该主题的广泛评论:http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) .但是让我们回到实际的例子:通过写

struct CarRetailer
{
    virtual std::vector<Car*> get_cars( ) = 0;
};

你 promise 的太多了,即 get_cars()函数将返回一个 vector ,您可以使用任何 Car 对其进行修改你要。这可能不是你的意思:

CarRetailer* ferrari = new ExpensiveCarsRetailer();
auto niceCars ferrari->get_cars();
niceCars.push_back(new Car{"Trabant"}); // you promised in the declaration that it was possible!

解决方案是减少您在根类中所做的“ promise ”,返回一个不能被“无关”对象更改的范围。一个只读的容器会很好,但遗憾的是,C++ 还没有(还?)智能到支持它的协变性:

struct CarRetailer
{
    virtual const std::vector<const Car*> get_cars( ) = 0;
};

struct ExpensiveCarsRetailer : public CarRetailer 
{
    const std::vector<const ExpensiveCar*> get_cars( ) = 0;
    // Alas, it won't override
};

但是范围(即指针对,希望 C++17 能提供更好的东西)可以:

struct CarRetailer
{
    virtual Car* const cars_begin( ) = 0;
    virtual Car* const cars_end( ) = 0;
};

struct ExpensiveCarsRetailer : public CarRetailer 
{
    ExpensiveCar* const cars_begin( ) override {return cars->begin();}
    ExpensiveCar* const cars_end( ) override {return cars->end();}

    private:
    vector<ExpensiveCar>* cars; 
};

(注意:我没有测试我的代码,所以请原谅可能的错误。但概念应该很清楚)

这个界面可能看起来比原来的界面更难看,但我的看法恰恰相反,因为它与强大的<algorithm>完美结合。 C++ 库并有助于以现代风格编写代码。这里有一个简单的例子:

any_of(dealer.cars_begin( ), dealer.cars_end( ),
       [](const auto& car) -> bool {return car.hasScratch();}
) ? complain() : congratulate();

这种设计最大的缺点是CarDealer类必须拥有 *cars容器并管理它的存在,即它必须关心它返回的指针保持事件状态。由于 C++ does not support covariance on them 不能返回智能指针这一事实使这变得很困难。 .此外,最终可能不得不维护一个 vector<Car>* 的容器。 , 如果 cars_begincars_end函数应该在重复调用时生成新的集合。因此,您需要根据您的具体用例来权衡我的建议的利弊。

就个人而言,如果我遇到同样的问题,我会使用模板化类并完全避免继承:恕我直言,此类问题很好地说明了为什么 OOP 有时会使事情复杂化而不是简化它们。

编辑

如果我很好理解为什么你觉得模板化的Dealers不会被指定为你的需求,我认为这个建议应该更合适:

struct ExpensiveCarsRetailer /* not derived, not templated */
{
    std::vector<ExpensiveCar> get_cars( ) { /*..*/ }
    // you can also return a vector of pointers or of unique_pointers, as you feel like.
};

struct CheapCarsRetailer /* not derived, not templated */
{
    std::vector<CheapCar> get_cars( );
};

使用模板函数代替重载:

template <typename T> print_car_table(T dealer) {
    // This will work on both Expensive and Cheap dealers

    // Not even a hierarchy for the Car classes is needed:
    // they can be independent structs, like the Dealer ones

    auto cars = dealer.get_cars();

    for (const auto& car : cars) { std::cout << car.name() << "\t" << car.color() << "\n"; }
}

template <typename T> apply_turbo(T dealer) {
    // This will work if dealer is an ExpensiveDealer,
    // and fail on compile time if not, as wanted

    auto cars = dealer.get_cars();

    for (auto& car : cars) { car.apply_turbo(); }
}

这个系统最大的好处就是你甚至不用提前规划接口(interface),每个类都可以只实现你需要的方法。因此,如果将来您添加 CarMuseum , 你可以决定实现 get_cars()得到print_car_table(T)使用它,但您可以自由地不创建任何其他方法。通过继承,您将被迫实现在基接口(interface)中声明的所有功能,或者创建许多零散的接口(interface)(class CheapDealer : public HasACarList, public HasAPriceList, /* yuck ...*/)。 .

这种模板化设计的缺点是 Dealer 类不是亲戚。这意味着您无法创建 vector<Dealer>也没有Dealer* (即使您使它们派生自一个微小的通用接口(interface),您也无法通过指向此类接口(interface)的指针调用 get_cars())。

为了完整起见,我会指出相反的动态解决方案:

struct Car {
    virtual int get_price() = 0;
    virtual void apply_turbo( ) = 0;
};

struct CheapCar: public Car
{
    int get_price( ) {/*..*/}
    void apply_turbo( ){throw OperationNonSupportedException();};
};

感觉 C++ 不是很地道,是吗?我觉得它很不雅观,但我仍然认为在丢弃它之前应该评估这个设计。优点和缺点或多或少与模板化解决方案相反。

最后,我想访问者模式或 Scala(提供非常强大的协方差工具)可以为您的问题提供其他替代解决方案。但是我对它们都没有经验,所以我留给其他人来说明。

关于c++ - 继承包含另一个派生类的 C++ 类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30360189/

有关c++ - 继承包含另一个派生类的 C++ 类的更多相关文章

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

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

  2. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  3. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  4. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

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

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

  6. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  7. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  8. ruby - 检查字符串是否包含散列中的任何键并返回它包含的键的值 - 2

    我有一个包含多个键的散列和一个字符串,该字符串不包含散列中的任何键或包含一个键。h={"k1"=>"v1","k2"=>"v2","k3"=>"v3"}s="thisisanexamplestringthatmightoccurwithakeysomewhereinthestringk1(withspecialcharacterslike(^&*$#@!^&&*))"检查s是否包含h中的任何键的最佳方法是什么,如果包含,则返回它包含的键的值?例如,对于上面的h和s的例子,输出应该是v1。编辑:只有字符串是用户定义的。哈希将始终相同。 最佳答案

  9. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  10. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

随机推荐