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

时间:2015-05-20 20:54:24

标签: c++ inheritance design-patterns vector dynamic-cast

将下一个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()未包含在公共接口CarRetailer中,因此,它可以返回std::vector<ExpensiveCar*>

API中的混合(Car*ExpensiveCar*的返回向量)非常难看,用户需要编写的代码才能使用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();
}

我确信我缺少一些有助于这种情况的设计模式,其中类继承树的耦合方式是其中一个继承tress的抽象类需要返回一个向量(或set,list,等其他继承树上的类)。我试图尽可能地避免动态铸造。

我还考虑过将CarRetailer设为模板类,因此:

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

然后制作:

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

但我认为这不会起作用,因为例如CarRetailer也可以开始销售摩托车(类似于汽车的结构)或自行车等(总是应用昂贵/便宜的模式)并最终出现这个数字需要在CarRetailer中定义的模板类将是巨大的。

2 个答案:

答案 0 :(得分:1)

&#34;包含继承类的类的耦合的整个概念&#34;被称为协方差。在维基百科上,人们可以找到对该主题的广泛评论: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!

解决方案正在减少&#34;承诺&#34;你在根类中做了,返回一个不能用&#34;无关的范围改变的范围&#34;对象。一个只读的容器会很好,但是,唉,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_begin函数应该在重复调用时生成新集合,则可能最终必须保留cars_end的容器。因此,您需要在具体用例中平衡我的提案的优缺点。

就个人而言,如果我遇到同样的问题,我会使用模板化的类并完全避免继承:这类问题恕我直言说明为什么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)使用它,但您可以自由地不创建任何其他方法。通过继承,您将被迫实现基本接口中声明的所有函数,或者创建许多分段接口(class CheapDealer : public HasACarList, public HasAPriceList, /* yuck ...*/)。

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

答案 1 :(得分:0)

为什么不像你使用get_cars_for_exhibitions一样拥有非虚拟功能来获取汽车?在示例中,您显示调用者知道他们有ExpensiveCarsRetailer因此他们不需要多态性。我假设你确实需要其他上下文中的继承和多态,但是很容易从ExpensiveCar指针的向量转换为Car指针的向量来实现CarRetailer接口:

template<typename T>
using Ptrs = std::vector<std::unique_ptr<T>>;

template<class T, class U>
Ptrs<U> upcast(Ptrs<T> input) {
  return Ptrs<U>(std::make_move_iterator(input.begin()),  
                 std::make_move_iterator(input.end()));
}
struct CarRetailer {
  virtual Ptrs<Car> getCars() = 0;
};

struct ExpensiveCarsRetailer : CarRetailer {
  Ptrs<ExpensiveCar> getCarsImpl() { ... }
  Ptrs<Car> getCars() override { return upcast<Car>(getCarsImpl()); }
};

如果你需要ExpensiveCar指针的向量,你可以调用非虚函数:

ExpensiveCarsRetailer ferrari;

auto expensive_cars = ferrari.getCarsImpl();
for (auto& expensive_car : expensive_cars)
  expensive_car->apply_turbo();

在多态上下文中,您可以调用虚函数:

CarRetailer* retailer = &ferrari;
auto cars = retailer->getCars();
for (auto& car : cars)
   std::cout << "Car price " << car->get_price() << "\n";