加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

对包含另一个派生类的C类的继承

发布时间:2020-12-16 07:12:21 所属栏目:百科 来源:网络整理
导读:将下一个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{ vir
将下一个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 *的向量,但是,内部的ExpensiveCarRetailer只创建了ExpensiveCar的对象.此外,get_cars_for_exhibitions()不包含在公共接口CarRetaile中,它可以返回std :: vector< ExpensiveCar *>代替.

API中的混合(Car *和ExpensiveCar *的返回向量)非常难看,用户需要编写的代码也是如此,以使用来自特定ExpesiveCarsRetailer的汽车列表的apply_turbo()函数.

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();
}

我确信我错过了一些有助于这种情况的设计模式,其中类继承的树以一种方式耦合,其中一个继承发辫的抽象类需要返回一个向量(或集合,列表等)另一个继承树上的类.我试图尽可能地避免动态铸造.

我还想过让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;
};

你承诺太多,即get_cars()函数将返回一个你可以用你想要的任何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!

解决方案是减少你在根类中所做的“承诺”,返回一个不能用“无关”对象改变的范围.一个只读的容器会很好,但是唉,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; 
};

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

这个界面可能看起来比原始界面更丑,但我的观点相反,因为它与强大的<算法>完美地集成在一起. 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以来无法返回智能指针这一事实使这变得困难.此外,如果cars_begin和cars_end函数应该在重复调用时生成新集合,则最终可能必须保留向量< Car> *的容器. .因此,您可以在具体的用例中平衡我的提案的优缺点.

在个人方面,如果我遇到同样的问题,我会使用模板化的类并完全避免继承:这样的问题恕我直言,这充分说明了为什么OOP有时会使事情复杂化而不是简化它们.

编辑

如果我理解为什么你觉得模板经销商不会满足你的需求,我认为这个提议应该更合适:

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(); }
}

这个系统的最大优点是你甚至不必提前规划接口,每个类都可以实现你需要的方法.因此,如果将来添加CarMuseum,您可以决定实现get_cars()以使print_car_table(T)与它一起工作,但您可以自由地不创建任何其他方法.使用继承,你是instad被迫实现在基接口中声明的所有函数,或者创建许多碎片接口(类CheapDealer:public HasACarList,public HasAPriceList,/ * yuck … * /). .

这种模板化设计的缺点是经销商类不是亲戚的结果.这意味着您无法创建向量<经销商>也没有经销商*(即使你使它们来自一个很小的通用接口,你也不能通过指向这种接口的指针调用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(提供非常强大的协方差设施)可以为您的问题提供其他替代解决方案.但我对这两者都没有经验,所以我把它们留给其他人来说明.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读