【问题标题】:Is polymorphism the best way to achieve this? (regarding function calls in derived classes) [closed]多态性是实现这一目标的最佳方式吗? (关于派生类中的函数调用)[关闭]
【发布时间】:2021-04-05 04:54:40
【问题描述】:

我有一个程序有四个类:

  • 车辆(基地)
  • 汽车(源自车辆)
  • 汽车(源自汽车)
  • 卡车(源自汽车)

在运行时,用户使用工厂函数生成作为“汽车”或“卡车”的汽车对象:

Automobile *Automobile::make_automobile(std::string choice) {
    if (choice == "car")
        return new Car;
    else if (choice == "truck")
        return new Truck;
    else
        return nullptr;
}

现在,“Car”类具有三个唯一的 setter(和三个匹配的 getter):

  • Set_NumDoors()
  • Set_SeatMaterial()
  • Set_Shape()

“卡车”类有一个唯一的 setter(和一个匹配的 getter):

  • Set_CargoWeight()

起初,这些函数只在它们自己的类中实现,但我无法在运行时调用它们,因为创建的对象是“汽车”对象。我已经使用具有本地覆盖的虚拟函数解决了这个问题。所以现在,“汽车”类的所有四个功能(以虚拟形式),默认情况下什么都不做。但是,当在对象上调用时,本地覆盖执行正确的功能。下面是一个示例,一切正常。

汽车类中的定义

std::string defaultStatement = "Function call is invalid on current object";
    virtual int set_seatMaterial(std::string choice){
        std::cout << defaultStatement << std::endl;
        return -1;
    }

在 Car 类中覆盖

    int set_seatMaterial(std::string choice) override { // override virtual base
        if (choice == "leather" || choice == "cloth"){
            seatMaterial = choice;
            return 0;
        }
        else
            return -1;
    }

然后我在 main() 中使用函数指针来适当地指向所需的函数:

if (user_choice == "seat"){
            std::function<int(Automobile*, std::string)> choiceFunction = &Automobile::set_seatMaterial;
            choiceFunction(userVehicle, seatMaterial);
}

我唯一的问题是 - 这是实现此功能的最佳方式吗?它可以工作,但现在我已经声明了“汽车”类中的每个函数,它已经有自己的 getter/setter。虽然我理解多态性的概念及其用处,但从某种意义上说,这似乎是重复。

或者有没有更好的方法从基类对象调用派生类函数?

【问题讨论】:

  • Automobile::set_seatMaterial 函数应该是抽象的而不是实现的。 IE。 virtual int set_seatMaterial(std::string choice) = 0;
  • choiceFunction 这样的设计原因是什么?简单的userVehicle-&gt;set_seatMaterial(seatMaterial) 有什么问题?
  • 用于运行时操作。我不知道他们要设置车辆的哪个属性,所以指针抓取他们选择的函数然后调用它。
  • "X 是实现 Y 的最佳方式吗?"恕我直言,这个问题很可能会用意见而不是事实和引用来回答。它应该进行更新,以便得出基于事实的答案。在那之前,我投票接近。
  • 我应该如何更新它以反映这一点?询问有关最佳做法的建议不是一个有效的问题吗?

标签: c++ class object c++11 polymorphism


【解决方案1】:

您的问题确实涉及一个主要的设计问题。一般来说,如果对象通常可以被同等对待,多态性是最合适的。只有在少数特定情况下才需要知道对象的确切类型。换句话说,在您的示例中,所有汽车都有一个重要的共同行为子集,而且大多数时候我们甚至不想知道我们在这里拥有哪种汽车。如果您发现自己一遍又一遍地对 Ted 的 if-else 链进行编程,这可能表明这些车辆的差异足以保证单独处理。如果您发现是这样,例如,您将没有指向汽车的指针向量,而是将卡车、汽车和半成品的向量分开,而不是分开处理。

但是让我们假设多态在这里是一种有效的方法,因为只有有限的几种情况需要对每种类型的汽车进行特殊处理:当您购买汽车时,当您修理一辆汽车时,当您处置一辆汽车时。例如,汽车购买者可能想要选择座椅材料,而购买卡车的公司则没有这种选择。显然,在各自的汽车“设置”中可能还有很多不同的东西。

这里的解决方案是为每种情况指定一个抽象的汽车基类方法。例如,每辆汽车都有一个“设置”例程,它与用户交互并获得所需的子类型特定信息。因此,您不再依赖 if-else 链,而是再次依赖多态性来为您做正确的事情。如果您可以将所有不同的行为适合您通过基类中的抽象方法描述的统一抽象模式,则此方法有效。缺点是对象可能需要有关其环境(iostream、图形上下文、数据库访问等)的信息,这与它们的实际功能无关。为了弱组件耦合,我们主要希望将这些东西与汽车分开。所以在这里,如果对象足够相似以至于它们需要一组相似的信息,这些信息可以传递给公共方法(而不是被相应的汽车对象知道),那么这种方法效果最好。

作为结论,这里有一个一般的经验法则:虽然 Ted 的回答没有错,但应该始终怀疑动态转换和 if-else 链;它们通常是缺乏抽象的标志。

【讨论】:

  • 嗨彼得,谢谢你的详细解释。我是 C++ 新手,所以你所说的一些内容让我无法理解,但随着我继续构建更复杂的程序,我会更多地反思它。我渴望尽早获得最佳实践,非常感谢您的建议。
【解决方案2】:

我已经使用带有本地覆盖的虚函数解决了这个问题。所以现在,“汽车”类的所有四个功能(以虚拟形式),默认情况下什么都不做。但是,当在对象上调用时,本地覆盖执行正确的功能。下面是一个示例,一切正常。

这使得Automobile 的实现者必须知道从它继承的所有类提供的所有方法,这使得它难以维护。

在您需要知道您正在处理的Automobile 类型的情况下,另一种选择是dynamic_cast

C++11 示例:

Automobile* unknown = ...;

if(auto car = dynamic_cast<Car*>(unknown)) {
    // call car-specific methods
    car->Set_NumDoors(4);

} else if(auto truck = dynamic_cast<Truck*>(unknown)) {
    // call truck-specific methods
    truck->Set_CargoWeight(1000);
}

Demo

【讨论】:

  • 是的,你完全正确,这正是我想要避免的问题。但是,如何按照您的建议调用“汽车”对象上的汽车特定方法?它不在可用方法列表中,因为它不是汽车对象。
  • @Cato 如果你使用Automobile* unknown = new Car;,你可以在上面car 不是nullptr 的块中调用car-&gt;Set_NumDoors(...);。 - 我将其添加到答案中。
  • 是的,我明白了,但问题是我不知道他们会选择哪款汽车。它始终是汽车,但只有在他们选择汽车时才会在运行时成为汽车对象。所以我不能提前调用这些方法(这就是我使用虚拟和覆盖的原因)。
  • 通用基类应该有一个 virtual 析构函数,就像我在演示中所做的那样。在你的情况下virtual ~Vehicle() = default; - 否则你会冒各种问题的风险。
  • @TedLyngmo 实际上正是一个问题:当您通过 Vehicle 指针销毁派生对象时的未定义行为,这通常在多态设计中完成,其中对象在工厂中创建,存储为基类指针 ( -collections) 并通过这些销毁。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-24
  • 2015-04-12
  • 1970-01-01
  • 2010-09-06
相关资源
最近更新 更多