依赖注入:我应该创建一个Car类来容纳其所有零件吗?


10

我的C ++应用程序中有很多赛车都包含在RaceTrack中。

每辆汽车由数百个零件组成。每个部分都依赖于另外一两个部分。

我已经在DI和Mark Seemann的书上阅读了很多SO问题,并且看起来我不应该为了容纳汽车零件而定义Car类,因为所有汽车零件都将相互依赖,而零件的本质就是汽车。我对吗?

因此,当我将所有赛车放入RaceTrack时,将没有汽车实体,而是很多汽车零件,这取决于彼此在赛道上的比赛??我对吗?

编辑:

很长时间以来,我都在编写汽车类课程,因为对于我来说,如果我编写汽车逻辑学,很显然我需要汽车类。但是使用DI并不是很明显。还是想知道,如果我没有定义角色,那么不创建Car类是DI的惯用做法吗?

我的意思是,可以在没有用于整体代表汽车的实例的情况下,为驾驶人员配备SteeringWheel,为维修人员提供BoltsAndNuts车轮以及其他各种有趣的接口吗?

Answers:


24

坐在赛道上的一堆汽车零件对任何人有什么好处?

如果您要学习的所有汽车类物品都是装汽车零件,那么它就像湿袋零件一样有用。

用法

作为驾驶员,我想要的是我可以控制的东西。当我要求速度时,它会做出响应。就像在轨道上一样处理。那可以一毛钱停下来。

我想要的是可用的汽车课程。我可以告诉自己做事而不必考虑化油器的工作原理。我只是想想油门踏板。我不担心这些如何连接。那是抽象的。

依赖注入

依赖注入与此无关。当我开车时,我并没有在考虑它的制造方式。只要有效,我就不在乎他们如何将它们组合在一起。

不,DI是让维修站工作人员在雨天开始下雨时迅速更换轮胎以获得更好的轮胎。能够做到这一点真是太好了,而不必跳入一辆完全不同的汽车。

DI实际上是关于遵循以下原则:将使用与构造分开。

可以以每小时90英里的速度安装新轮胎的汽车听起来很酷,但我认为在轮胎上安装设备不会赢得任何比赛。

DI是关于使零件安装的,这样您的维修人员就可以使用它们。当您new在同一地方使用时,您可以对行为进行编程,就像将化油器焊接到位。确保乙炔炬可以将其除去,但请考虑首先使用螺母和螺栓。

那就是DI。当然new,一旦意识到自己想要的东西就不像想起来那么容易。相反,您必须编写单独的代码来知道如何制造汽车。但这肯定会使以后更改内容变得更容易。这意味着您不必在轨道上拖拉汽车装配厂。

施工

某处的某处必须知道轮胎是否为固特异。那该把车的构造代码放到哪里呢?如果不是赛车,那么维修人员呢?不行 否。所有这些都有行为代码。比赛中必须执行的代码。在比赛开始前的某个地方,行为守则应将赛车移出现场。马克·西曼斯(Mark Seemans)将这个地方称为“ 合成根”。大多数人称之为主要。

这是一个简单的模式。首先,构造对象图,然后对对象图中的一个对象调用一种行为方法。而已。这是唯一必须将构造和行为结合在一起的地方。

这并不意味着构造必须是一堆程序代码,它们全部按顺序排列在main中。您可以自由使用每种语言的工具进行构建。只是不要将其与行为混为一谈。

用这种语言而不使用某些DI框架或IoC容器进行此操作称为纯DI。效果很好。已经很久了。我们曾经称其为引用传递

DI工具

DI工具为您提供的是将构造细节移入另一种语言(xml,json等),从而将构造与行为分开。如果您不信任其他程序员不要new在不该使用的情况下使用它们,那可能会很有吸引力。

缺点是让DI工具的细节散布在整个代码库中是很诱人的。有时会使用专有注释感染代码库。他们提供的例子肯定鼓励了这一点。该工具趋向于进入语言领域,直到您不仅可以将工作宣传为Java编程工作,而且可以宣传为Java / Spring编程工作。

设计原则

很长时间以来,我都在编写汽车类课程,因为对于我来说,如果我编写汽车逻辑学,很显然我需要汽车类。但是使用DI并不是很明显。还是想知道,如果我没有定义角色,那么不创建Car类是DI的惯用做法吗?

我认为您正在学习抽象,并正在改变确定类的方式。那很好。但这与DI无关。DI不能帮助您决定是否需要上汽车课。如果轮胎是固特异轮胎,DI可以帮助您避免让汽车知道和进行保养。DI可帮助您了解汽车是否是日本制造的。

软件设计中最基本的问题之一是“对什么了解什么?” 这是UML图向您展示的主要内容。当您完成某件事情时,它已经成为您现在所绑定的具体事物的接口。现在,汽车必须知道轮胎是固特异的。如果米其林想赞助您,那可真糟。

避免这样做是遵循依赖倒置原则的。从形式上讲,高级模块(如汽车类)不应直接依赖于低级模块(如GoodyearTire类)。它应该取决于抽象(例如Tire接口)。

一种避免这样做的方法称为控制反转。这里的重点是改变控制流程。轮胎是在移动汽车还是在汽车在移动轮胎?考虑正确的方法使我们不能将汽车和轮胎静态地耦合在一起。DI是遵循控制反转的一种特殊方式。

这些都不会告诉您是否需要上汽车课。如果您正在编写“汽车逻辑”,那么最好有一个地方保存它,而不是分散到每个地方。只是不要误以为汽车构造逻辑与汽车行为逻辑相同,因此所有这些都必须存在于同一个地方。但是,如果您没有定义的汽车侧倾,则也不需要。如果您愿意,可以在赛道上骑摩托车。

我的意思是,可以在没有用于整体代表汽车的实例的情况下,为驾驶人员配备SteeringWheel,为维修人员提供BoltsAndNuts车轮以及其他各种有趣的接口吗?

DI或不使用DI,可以有一个代表整个汽车的实例,但是如果不需要,我不想直接知道那个实例。给我一个汽车摘要,这样我在使用时就不必担心它是用汽油,柴油还是电力运行的。这只是我在构建或维护它时应该关心的事情。如果使用car的代码不必知道或关心它的工作原理,那就很好。“我不知道。我不想知道。”


如果您不使用汽车和维修人员的隐喻来使用它们来表示代码的问题,那将很酷。但仍然是一个很好的答案。
S.TarıkÇetin17年

6
我一直以为控制反转是当您向左转而汽车向右转时
mkrieger1 17-4-10

CandiedOrange,如果没有有意义的接口和明确定义的责任,那么就没有Car类吗?在我的项目中没有Car.h。和同事一起研究我的代码,想知道这是汽车零件网上商店还是车库管理应用程序?:)
Anton Petrov

@AntonPetrov“如果它看起来像鸭子,又像鸭子,却需要电池,则可能是错误的抽象”。良好的名字非常重要,很难想到,但是应该更改以反映明确定义的责任和有意义的界面,以免出现意外。如果我读了名字,然后看一下界面并认为“这几乎是我所期望的”,那么您就有个好名字。
candied_orange

15

似乎我不应该只为容纳汽车零件而定义Car类,因为所有汽车零件都将相互依赖,而零件的本质就是汽车。我对吗?

没有。

依赖注入的目的根本不是阻止依赖。恰恰相反。

依赖注入的问题是:汽车哪里获得零件?它们是在哪里创建的以及如何创建的,汽车如何获得对它们的引用?

天真地,汽车会通过调用自己创建零件new。这很简单明了,但是却很不灵活。汽车必须了解创建零件所需的一切,并且,如果您想拥有两辆具有不同零件的汽车,那么逻辑也必须纳入汽车中。

通过依赖注入,所有这些逻辑都可以从车中取出并放入配置组件中,该组件将创建所有零件并连接参考。完全配置的依赖项将相互注入


1
最后,对dependency injection概念进行了理性的解释。
anatoly techtonik

1
我想说的是,“天真的”方法更常见的系统不是汽车会叫新车,而是汽车类别的用户将其零件分配给它的属性,与此同时,汽车类别也会在不确定的状态。DI提供了一种机械地确保班级有效性的方法。
whatsisname

2
@whatsisname我可以轻松地使用DI来创建无效和一半初始化的对象。验证不是DI责任。也不是一成不变的。DI可以负责构建有效且不变的对象。DI无法强制要求这些是构造和使用这些对象的唯一方法。对象必须执行此操作。
candied_orange

@CandiedOrange:所以您可以轻松地使用任何技术来使某些东西变得半确定,那又如何呢?在构造函数中要求依赖项并使它们不可变不是灵丹妙药,但这仍然是防止许多常见不良情况的非常有用的方法。
whatsisname

0

因此,当我将所有赛车放入RaceTrack时,将没有汽车实体,而是很多汽车零件,这取决于彼此在赛道上的比赛??我对吗?

好。是的,没有。汽车实体仍然有效。汽车不仅仅是零件的总和。您还需要一个驱动程序(暂时)。很多时候赛车也有名字,更不用说赛车的品牌和型号了。

是的,汽车基本上是零件的容器,但是零件的这种包装和协作构成了更大的东西:汽车。

真的,不。汽车不仅是随机的金属和塑料袋。这是一台机器。

在这种情况下,依赖注入并不是过大的。还要制造汽车,这可能是工厂类或Builder对象。汽车不应该知道如何制造自己。


2
您可以不带工厂或构建器对象而拥有DI。简单的构造函数参数是适用于多种情况的有效方法。
whatsisname
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.