1

首先让我说,我对 OO 模式、实践、干净代码等方面的经验并不丰富。我实际上正在学习所有这些技术。

最松耦合的方式是使用原始类型来构造新对象或执行方法,但我认为这不切实际,对吗?因为它更容易出错。我可以放弃让我们说代表根本不存在的 Id 的整数。如果我要使用一个对象,我实际上知道它有有效的数据,否则该对象将不会被创建(异常)或处于我必须检查的无效状态。

这篇文章说为此使用具体对象是邪恶的,我应该交出它们的接口(你们都知道,我猜)。对具体类型(而不是接口)的更改会导致依赖类型“崩溃”。在所有情况下都是这样吗?对于封闭的单一项目环境也是如此吗?我只会明白,如果接口 - 一旦编写 - 就无法触及并且永远不会再被修改/重构。

4

3 回答 3

2

您应该在需要时使用所有东西。只有在有意义的情况下才使用原语。与具体类型相同。确实,与抽象“耦合”更好,但是在许多情况下,您不会拥有一个,也不需要一个。您可以从您正在使用的所有对象中提取接口,但如果您没有真正的理由这样做(多态性),那将毫无意义。

你必须思考。您可以开始使用具体类型,但随后您会发现您确实需要一个抽象而不是真正的具体类型(例如,如果您知道存储可以更改,那么抽象存储是一种非常常见的情况)。一个对象可以依赖于数据库抽象(抽象类或接口),而不是依赖于 Mysql 数据库对象,它允许您切换任何实现(Mysql、MMSql 甚至 NoSql)。但是,如果您正在编写一个仅使用 mysql 的小型应用程序,则只需直接使用具体类型即可。

您可能想要提取接口还有一个原因,那就是测试。当然,仅当具体类型将用作依赖项并且它具有足够复杂的行为时,才提取接口。如果依赖项只是一个简单的 DTO(数据传输对象),则不需要抽象它。

这些东西大部分都来自经验,但经验法则是从具体类型开始,然后在需要时将其抽象出来。如果一个对象已经实现了一个包含您想要的功能的接口,请直接使用该接口。

于 2012-11-23T11:49:42.767 回答
2

我想这个问题会引发很多问题。我会尽量保持简短:

我可以放弃让我们说代表根本不存在的 Id 的整数。

从我的角度来看,程序是代表您的问题域的(计算)模型(可以与物理学家或天文学家编写方程式来表示现象进行类比)。当您使用对象建模时,您所做的是使用某些特定规则创建该域的表示。因此,回到您的问题,您可以在概念上用整数表示什么是 ID,但随后您的问题域中将有一个未正确表示的概念(例如,因为存在无效 ID 的整数)。此外,除了概念问题之外,问题在于您不能向整数添加(并因此委托)新行为,如果可以(例如,在 Smalltalk 中,一切都是对象,您可以扩展任何类),那也是错误的从建模的角度来看。作为一般经验法则,当我必须在不应该有给定责任的对象中编写行为时,我认为模型缺乏抽象。在这种情况下,就像有一个Util带有类方法的类isValidId

如果我要使用一个对象,我实际上知道它有有效的数据,否则该对象将不会被创建(异常)或处于我必须检查的无效状态。

同意 100%。我写了几篇你可能会觉得有用的文章(免责声明:我在 Quanbit Research 工作)

这篇文章说为此使用具体对象是邪恶的,我应该交出它们的接口(你们都知道,我猜)。对具体类型(而不是接口)的更改会导致依赖类型“崩溃”。在所有情况下都是这样吗?

涉及对象、类型和接口的故事很长。总结一下,理想情况下,您应该针对接口而不是具体类进行编程,因为(理论上)您应该只关心给定对象(例如参数)实现一组具有预定义语义的消息。然而,如果你走这条路,在实践中你会发现一个类通常实现多个接口,并且让所有接口与类同步的簿记是令人望而却步的。我通常使用动态类型语言,所以在大多数情况下这对我来说不是问题,但如果我必须使用静态类型语言,当系统必须与项目外部的代码形式交互时,我会使用接口或在模块之间的 API 中。换句话说,我会尝试降低系统“边界”中的耦合。

对于封闭的单一项目环境也是如此吗?我只会明白,如果接口 - 一旦编写 - 就无法触及并且永远不会再被修改/重构。

我不得不在这里不同意。作为计算模型的程序反映了我们在问题域的给定时间点所知道的内容。因此,我们对它的工作越多,我们对它的了解就越多。编程涉及学习,当我们学习时,我们会更好地理解事物;因此我们的模型发生了变化。随着我们的模型发生变化,我们用来表示它们的元素也会发生变化(如类或接口)。随着时间的推移,你会发现你的模型变得更加健壮,概念上的变化会变慢,并且在某个时候你会拥有一个稳定的模型。但是变化是你应该期待的事情:)。

高温高压

于 2012-11-23T12:06:43.430 回答
2

我认为答案(通常是这样)是“视情况而定”。决策受以下因素影响:

  1. 您正在使用哪些类型的对象?领域对象(表示业务领域中的概念)或服务(例如提供查找或保存操作)。

  2. 您的应用程序有多大/复杂?

  3. 您是否“拥有”所有正在使用的对象?您正在使用的对象是否可能会被您无法控制的其他人更改?

在复杂到足以证明域模型的项目上,我喜欢使用以下设置:

  1. 一个数据访问层,其中包含获取 id 并返回域对象的“查找器”服务对象,以及获取域对象的“保存”服务对象。

  2. 包含域对象的域模型,这些域对象仅在其方法中采用其他域对象。

  3. 接受 id的服务层,使用数据访问层检索域对象,触发域模型中的域操作,然后使用数据访问层保存更改。

  4. 一个或多个使用服务层的 UI 层,提供 id。

我将服务对象放在接口后面的数据访问层和服务层中,因为我希望在我的单元测试中可以轻松地模拟这些组件。除非我发现这样做有特定的好处,否则我通常不会为我的域对象制作接口。

最后,在 A 类对 B 类有具体引用的情况下,我看不出对 B 类的与 A 类使用的接口无关的部分的更改将如何破坏 A 类。

于 2012-11-23T13:36:38.333 回答