对包含另一个派生类的C类的继承
将下一个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,则需要实现 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(提供非常强大的协方差设施)可以为您的问题提供其他替代解决方案.但我对这两者都没有经验,所以我把它们留给其他人来说明. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |