【问题标题】:Whether to model a car object (and its parts such as engine) with has-a (composition) or is-a (inheritance)?是否使用 has-a(组合)或 is-a(继承)对汽车对象(及其部件,如引擎)进行建模?
【发布时间】:2010-12-27 03:32:24
【问题描述】:

我正在开发一个包含 Car 对象的类库。

难题在于,Car 本身将是一个类,其中包含诸如注册号等字段以及有关汽车的其他一般信息。

但是汽车有引擎、底盘等。这些对象也需要建模。它们应该是嵌入在 Car 中的类吗?如果不是,嵌入式类的使用场景是什么?

我了解到组合是“一部分”,因此您可以对单独的类进行建模并使用引擎类型,例如,在汽车的字段级别来实现这一点。但是,“聚合”是与在 ctor 中传递的类型“具有”的关系,也适用(汽车“具有”引擎)。

我该往哪边走?

编辑:我目前正在做作业,因此我没有回复。类库用于基于汽车的 Web 应用程序。我是一名专业开发人员(我以 .NET 开发为生,但作为一名初级开发人员)所以这不是家庭作业问题。

谢谢

【问题讨论】:

  • 如果您解释这个类库的目的,可能会提供更多有用的建议。它适用于什么类型的应用程序?
  • 听起来像“学术兴趣”又名家庭作业
  • 为什么需要建模?您是否也打算对制造它们的原子进行建模?
  • 我推荐使用 Lisp。它同时内置了 Car 和 Atoms。

标签: oop inheritance design-patterns composition car-analogy


【解决方案1】:

这真的取决于你的应用程序。

例如,您可以将轮子实现为单独的类,包含有关轮胎上的轮胎、磨损程度等信息,但如果您的应用甚至不关心轮子,那么整个类都是浪费代码。

我可以看到三个组合用例:

  • 拥有的类过于复杂,应该分解。
  • 拥有的类具有可以映射到类中的一组属性的多个副本。这允许您将所有这些属性绑定在一起。
  • 包含的对象可能需要与拥有它的对象分开检查或考虑(例如,您可能希望将 Engine 对象移到另一辆车上),或者可能作为一个单元进行更换。

总结:使用组合作为封装复杂性或消除重复的工具。如果它不能满足这些目的之一,则可能不值得为此创建一个新类。

【讨论】:

  • 我同意这一点。虽然将汽车的每个部分都编写为一个单独的类很诱人,但这样做可能会过度设计。
  • 强烈反对。许多小而简单的类促进了职责分离和目的清晰、可读性、稳定性、松散耦合的代码。几乎所有 OOP 文献都提倡组合优于继承,并减少对类的责任。
  • 如果类只有50行,那已经够清楚了。将其拆分为额外的类并不会产生清晰性,而是会产生复杂性。如果代码增长,您总是可以将这些部分重构为单独的类。
  • 但是,不将轮胎单独分类违反了单一责任原则。所以,我相信如果 Tire 具有单独的行为和属性,最好将其设为单独的类(如 Engine)。
【解决方案2】:

一个类应该有尽可能少的职责,并封装其他功能并将其委托给其他类。许多做一件事的小而简单的类是可读、稳定的代码库的标志。

是的,汽车将“拥有”引擎,但我建议为此使用接口以及类似的“拥有”关系。同样,根据教授的不同,您可能会因让工厂制造不同的汽车而获得加分(合适,不是吗?):

public class Car
{
    private Engine engine;
    public Car(Engine engine)
    {
        this.engine = engine;
    }

    public void accelerate()
    {
        this.engine.goFaster();
    }

    public void decelerate()
    {
        this.engine.goSlower();
    }

}

public interface Engine
{
    public void goFaster();
    public void goSlower();
}


public class ReallyFastEngine implements Engine
{
    public void goFaster()
    {
    // some code that goes really fast
    }
    public void goSlower()
    {
    // some code that goes slower
    }    
}

public class NotAsFastEngine implements Engine
{
    public void goFaster()
    {
    // some code that goes not as fast
    }
    public void goSlower()
    {
    // some code that goes slower
    }    
}

public class CarFactory()
{
    public static Car createFastCar()
    {
         return new Car(new ReallyFastEngine());
    }

    public static Car createNotAsFastCar()
    {
         return new Car(new NotAsFastEngine());
    }
}

【讨论】:

    【解决方案3】:

    鉴于这是家庭作业,并且根据您的导师/教授/老师的倾向,您可能最好沿着为引擎、车轮等编写单独的课程的路线前进。即使它可能完全过度设计,并且您的应用程序可能不关心它们,您的作业也可能会被以下标准标记:

    “他们是否确定了一个引擎类”

    “是否有像 Start() 这样的合理方法”

    “将他们标记为一个实际上更简单的大类中的所有内容,因为他们显然不了解组合”

    或者其他什么,而不是这个线程中更务实的人应用于他们自己的设计的那种标准。

    【讨论】:

      【解决方案4】:

      仅将汽车模型分解为汽车范围之外的单独实体。另一种思考方式是,您真的了解转动钥匙时汽车是如何启动的吗?就典型的驱动程序而言,引擎盖下的一切都是一个大(且嘈杂)的黑匣子。汽车工程师了解需要车主维护的常见部件,并针对不同级别的用户交互进行了明确设计,例如油尺或冷却液储液罐加注盖。

      你能为汽车的每一部分建模吗?当然。对单个火花塞进行建模是否有帮助?可能不是。

      您需要具有不同属性(如颜色或尺寸)的汽车吗?您是否需要具有不同功能的汽车,例如载客或牵引能力?一个不同的地方是您是否需要具有不同行为的汽车。这是您真正需要考虑对具有属性的 Driver 对象进行建模的地方,从简单的属性(如反应时间)到复杂的属性(如攻击性)。

      将车辆建模为面向对象或继承的示例是有问题的,因为这些示例并不能真正解释定义类的基本属性之间的真正区别。这对 StackOverflow 来说并不新鲜,但这个问题也不是重复的,see this SO thread。我和我的一个朋友进行了同样的讨论并发布了log of it on my blog。阅读 FAA 认可的不同飞机类型以及如何细分每种类型的规定。有很多不同类型的飞机,最大的区别是有动力和无动力的。

      查看definitions used by the FAA

      飞机是指使用的设备 或打算用于飞行 空气。

      飞机是指发动机驱动的 比空气重的固定翼飞机, 在飞行中由 空气的动态反应 它的翅膀。

      飞艇是指发动机驱动的 比空气轻的飞机 引导。

      还有比空气轻和比空气重的。热气球是无动力的,比空气轻。飞艇是有动力的,比空气轻。滑翔机是无动力的,比空气重。波音 757 具有动力且比空气重,但增加了另一类“固定翼”,这与同样具有动力且比空气重但属于“旋转翼”的直升机不同。

      这里是表格形式的前四个:

                       |  Powered   |    Unpowered
      ---------------------------------------------------
      Lighter-than-air |  Blimp     |    Hot-air balloon 
      Heavier-than-air |  737       |    Glider
      

      你明白了。

      您不能只说将发动机与汽车分开建模,因为没有发动机的汽车可能是完全不同的动物。没有引擎的汽车与拖车完全不同,拖车也没有引擎,但也永远不会。在这些情况下,“is-a”和“has-a”都不适合我们构建对象的具体方式。您不会将飞艇宣布为“比空气轻”的飞机,热气球也是如此。除了它们所利用的物理学之外,它们都比空气轻这一事实并没有使它们以任何方式相关。区别很重要,因为适用的规则和法规不同。从另一个角度来看,我们不会将飞艇描述为“有”引擎的热气球。飞机在物理上没有关系,关系是它们应该如何处理。

      如果您不需要将对象定义到该详细级别,您可能也不需要将它们建模到该详细级别。

      【讨论】:

        【解决方案5】:

        Car 将是一个顶级层次结构对象。包括简单的字段,如编号、ID 或描述。 并且会有像Engine这样复杂的字段,它本身就是一个对象。

        所以汽车看起来像:

        class Car{
             String ID;
             Engine engine;
        }
        

        有一个关系。

        【讨论】:

        • 我认为他已经知道这一点,并且正在寻求有关哪些场景值得一提的指导。
        【解决方案6】:

        您可以有一个标准来决定引擎、底盘等的类。
        需要作为内部类(嵌入类)存在是
        的实例 这些类可以在您的应用程序的其他地方使用。在这种情况下,
        决定很简单,就是让这些类分开存在
        (不是内部类)。

        即使这些类没有在您的应用程序的其他地方使用,那么其他
        标准可以是可测试性。将这些类嵌入到您的
        设计是否有可能进行单元测试以适当地测试您的
        提供良好覆盖率的代码。

        例如,如果你创建了一个引用一个实例变量
        Engine 对象和此变量正在 Car.And
        的构造函数中初始化 您的 Engine 类有一些需要测试的方法。那怎么能
        您添加单元测试来检查引擎类中的代码?可能你会
        在 Car 类中有一些方法可以公开行为或 Engine 类允许
        你写单元测试。那么问题是是否需要曝光
        Engine 类的行为不是更好吗 Engine 类
        独立存在?

        或者,可能不需要显式测试
        中的方法 Car 中的引擎类和单元测试方法涵盖了引擎类代码
        也是。然后它反映了 Engine 类与 Car 类的紧密集成
        并且意味着它可以保留为内部类。

        【讨论】:

          【解决方案7】:

          这取决于你想要做什么。在不了解用例的情况下尝试设计“汽车”类(或任何其他类)是徒劳的。

          根据您尝试启用的用例,您将设计不同的类及其关系和交互。

          【讨论】:

            猜你喜欢
            • 2011-07-29
            • 2010-10-02
            • 1970-01-01
            • 2016-03-23
            • 1970-01-01
            • 2011-01-14
            • 2018-11-13
            • 2021-11-02
            相关资源
            最近更新 更多