为什么紧耦合不好但强类型好?

13

我很难看到松散耦合的实际好处。为什么要花那么多精力使某些东西灵活地与其他对象一起工作呢?如果您知道需要实现什么,为什么不专门编写针对该目的的代码?

对我来说,这类似于创建未经分类的变量:它非常灵活,但由于可能传递了意外的值,它会自己开启问题。而且,这使得阅读更加困难,因为您无法明确知道正在传递什么内容。

然而我感觉强类型是被鼓励的,而松散的耦合是不好的。

编辑:我觉得我对松散耦合的解释可能不恰当,或者别人误解了我的想法。对我来说,强耦合是当一个类引用了另一个类的具体实例时。松散耦合是当一个类引用了另一个类可以实现的接口时。

那么我的问题是为什么不直接调用一个类的具体实例/定义呢?我将其比作特定定义所需的变量类型。我一直在阅读有关依赖注入的文章,他们似乎认为松散耦合是更好的设计。


5
这句话的意思是两件事情完全没有关系。 - user177800
我不会投票关闭这个问题,但在我看来,你非常接近“争论”的界限;特别是因为你已经声明了你知道代码应该松散耦合的原因...但你只是不同意。 - Andrew Barber
我不同意,因为我感觉这个概念是“要灵活处理因为你不知道需要什么”,但这并不是变量类型的看法。 - JLX
@JLX... 更改代码以适应新变量类型通常相当容易。但由于您需要更改特定算法的工作方式,重新设计大段代码就不那么容易了。 - Gerald
说代码应该松散耦合就像说汽车应该使用汽油一样。但是喔哟,一些汽车使用柴油。天啊。那些都是坏车!我说坏! - David Titarenco
一辆松散耦合的汽车更容易使用汽油或柴油运行 ;) - Gerald
9个回答

14

首先,你在进行苹果和橙子的比较,所以让我从两个角度来解释。类型指的是对值/变量执行操作的方式,以及它们是否被允许。耦合性与内聚性相反,指的是一个或多个软件组件的架构。这两者没有直接的关系。

强类型与弱类型

强类型的语言通常是一件好事,因为行为是明确定义的。例如,从维基百科上可以看到以下两个例子:

弱类型:

a = 2
b = '2'

concatenate(a, b) # Returns '22'
add(a, b)         # Returns 4

以上内容可能有些令人困惑并且定义不太清晰,因为一些语言可能使用ASCII(可能是十六进制、八进制等)数字值来进行加法或连接,因此容易出现错误。此外,很难确定a最初是整数还是字符串(这可能很重要,但语言并不在意)。

强类型语言:

a = 2
b = '2'

#concatenate(a, b)     # Type Error
#add(a, b)             # Type Error
concatenate(str(a), b) # Returns '22'
add(a, int(b))         # Returns 4
如您所见,这里的一切都更加明确,您知道变量是什么,以及何时更改任何变量的类型。
维基百科表示:
弱类型的优点在于相对程序员而言需要更少的工作,因为编译器或解释器会隐式地执行某些转换。然而,一个缺点是弱类型编程系统在编译时捕获的错误较少,其中一些可能在测试完成后仍然存在。两个常用语言支持许多种隐式转换的语言是C和C ++,有时声称这些是弱类型语言。然而,其他人认为这些语言对不同类型的操作数可以混合使用的限制足够,应将其视为强类型语言。
强类型与弱类型都有其优点和缺点,没有好或坏之分。了解差异和相似之处非常重要。
松散耦合与紧密耦合
直接来自维基百科:
在计算机科学中,耦合或依赖性是每个程序模块依赖于其他每个模块的程度。
通常将耦合与内聚性进行对比。低耦合通常与高内聚性相关,反之亦然。 耦合和内聚性的软件质量指标是由Larry Constantine发明的,他是结构化设计的原始开发人员之一,也是这些概念的早期支持者之一(另请参见SSADM)。低耦合通常是良好结构化计算机系统和良好设计的标志,并且当与高内聚性结合使用时,支持高可读性和可维护性的一般目标。
简而言之,低耦合是非常紧密、可读性强且易于维护的代码的标志。在处理大型API或大型项目,其中不同的部分相互作用以形成整体时,高耦合是首选。没有好或坏之分。有些项目应该紧密耦合,即嵌入式操作系统。其他项目应该松散耦合,即网站CMS。
希望我在这里提供了一些信息 :)

7
这个问题指出弱类型/动态类型确实是松散耦合概念的一个逻辑扩展,对程序员来说偏爱其中之一却不喜欢另一个是不一致的。
松散耦合已经成为一个流行词汇,许多程序员不必要地实现接口和依赖注入模式,或者更常见的是他们自己混淆的版本,基于某种未来需求的可能性。事实上,这样做增加了额外的复杂性,并使代码对将来的开发人员来说难以维护。唯一的好处是如果这种预期的松散耦合恰好使未来的需求变化更容易实现,或者促进代码重用。然而,通常情况下,需求变化涉及足够多的系统层次,从UI到存储,使得松散耦合根本不能提高设计的鲁棒性,并使某些类型的琐碎变化更加繁琐。

5
您说的松耦合在编程中几乎被普遍认为是“好”的。要理解为什么,让我们看一下紧耦合的一个定义:
您说如果A必须更改只是因为B更改了,那么A与B是紧密耦合的。
这是一个从“完全解耦”(即使B消失了,A也会保持不变)到“松散耦合”(对B进行某些更改可能会影响A,但大多数演化性更改不会)到“非常紧密耦合”(对B进行的大多数更改将深刻影响A)的尺度。
在面向对象编程中,我们使用许多技术来减少耦合 - 例如,封装有助于将客户端代码与类的内部细节分离开来。此外,如果您依赖于接口,则通常不需要太担心实现接口的具体类发生变化。
顺便说一句,您说的类型和耦合之间存在关系。特别是,更强和更静态的类型倾向于增加耦合。例如,在动态语言中,您有时可以用字符串代替数组,因为字符串可以看作是字符数组。在Java中,您不能这样做,因为数组和字符串是无关的。这意味着,如果B以前返回一个数组,现在返回一个字符串,它肯定会破坏其客户端(这只是一个简单的人为例子,但您可以想出许多更复杂和更有说服力的例子)。因此,更强的类型和更静态的类型都是权衡。虽然更强的类型通常被认为是好的,但是静态与动态类型之间的偏好很大程度上取决于上下文和个人品味:如果您想要一场好的战斗,请尝试在Python程序员和Java程序员之间进行辩论。
最后,我们可以回到您最初的问题:为什么松散耦合通常被认为是好的?因为存在无法预见的变化。当您编写系统时,您不可能知道它将最终在两个月或也许两个小时内发展成哪个方向。这既是因为需求随时间变化,也是因为您通常在编写系统之后才真正理解该系统。如果整个系统非常紧密耦合(有时称为“大球泥”),则系统中每个部分的任何更改最终都会影响到系统的每个其他部分(“非常紧密耦合”的定义)。这会导致非常不灵活的系统,最终会晶化成一团僵硬、难以维护的东西。如果您在开始处理系统时就有100%的远见,那么您就不需要解耦了。

另一方面,正如您所观察到的那样,解耦会增加复杂性。简单的系统更容易改变,因此程序员的挑战在于在简单和灵活之间取得平衡。紧密耦合通常(并非总是)使系统更简单,但代价是使其更加僵化。大多数开发人员低估了未来的变化需求,因此常见的启发式方法是使系统的耦合度比您想象的要小,只要这不会使其过于复杂。


我觉得我需要在这里添加一个附言:松耦合并不是降低更改成本的唯一方法。像测试驱动开发这样的技术往往会导致系统非常简单,因此相对便宜,同时仍然能够完成今天需要完成的工作。我是在假设您正在进行传统的前期设计,也称为“试图预测未来”的情况下回答的。一旦您转向纯TDD,简单/灵活的困境就基本上成为了一个无关紧要的问题。 - Paolo Perrotta
“只要不让它过于复杂”是需要记在心里的关键点。如何知道一个设计是否过于复杂?根据我的经验,经验是最好的指南 :) - Jordan Rieger
测试也可以添加耦合和单元测试,针对具体值进行测试比如集成或端到端测试更糟糕。但是像测试、类型、注释和约束/契约等东西被认为是您规范的一部分,您必须确保您的规范对所有可能的用例都是开放的,同时对于常见的用例易于使用。 - aoeu256
测试还可以添加耦合和单元测试,测试具体值的测试比如集成或端到端测试更糟糕,但是像测试、类型、注释和约束/合同这样的东西被认为是您规范的一部分,您必须确保您的规范对所有可能的用例都是开放的,同时对于常见的用例易于使用。 - undefined

2
如果您知道您需要实现什么,为什么不为此编写特定代码呢?
简短回答:您几乎从不会完全知道自己需要实现什么。需求会发生变化,如果您的代码一开始就是松散耦合的,那么将来适应变化也不会太困难。

1
但实际上,不是“你几乎从来不知道最终需要实现什么”吗?将某些东西松散耦合如何使其更易于适应?这就像说,“这个变量应该是一个字符串,但由于我不能确定,最好将其设置为未类型化”。 - JLX
2
现在你假设有一个结尾 ;) - Kevin Stricker
答案是,如果你认为强类型 != 松耦合,那么你的思考尺度太小了,无法理解为什么松耦合是好的。 - Kevin Stricker
对于字符串这个东西,如果你有测试的话,你会发现在测试中它接受了一个字符串,但是字符串只是其中一种可能性。类型推断也可以告诉你,嘿,这个变量在文本参数中被使用了。 - aoeu256
对于这个字符串的问题,如果你有测试的话,你会发现在测试中它接受了一个字符串,但是字符串只是其中一种可能性。类型推断也可以告诉你,嘿,这个变量在文本参数中被使用了。 - undefined

2
强类型是好的,因为它通过在编译时而不是运行时抛出错误来防止难以发现的bug。
紧密耦合的代码是不好的,因为当你认为你“知道需要实现什么”时,通常是错误的,或者你还不知道你需要知道的一切。也就是说,你可能会发现你已经做过的一些事情可以在代码的另一个部分中使用。然后你可能决定紧密耦合两个不同版本的相同代码。再后来,你必须稍微更改某个业务规则,你必须改变两个不同的紧密耦合代码集,也许你会把它们都弄对,这最多要花费两倍的时间……或者在最坏的情况下,你会在一个代码集中引入一个bug,但在另一个代码集中没有发现,然后你会陷入麻烦。
或者你的业务增长比你预期的快得多,你需要将一些数据库组件转移到负载平衡系统上,所以现在你必须重新设计与现有数据库系统紧密耦合的所有东西以使用新系统。
简而言之,松散耦合会使软件更易于扩展、维护和适应不断变化的条件和要求。
补充:我感觉我的理解松散耦合的方式可能不对,或者其他人可能误读了。对我来说,强耦合是指类引用另一个类的具体实例。松散耦合是指一个类引用另一个类可以实现的接口。
我的问题是为什么不明确地调用类的具体实例/定义?我将其比作明确定义所需的变量类型。我已经阅读了一些关于依赖注入的文章,他们似乎将松散耦合视为更好的设计。
我不太确定你的困惑在哪里。例如,假设你有一个应用程序,重度使用数据库。你有100个不同部分需要进行数据库查询。现在,你可以在100个不同的位置使用MySQL++,或者创建一个单独的接口来调用MySQL++,并在100个不同的位置引用该接口。
现在你的客户说他想使用SQL Server而不是MySQL。
哪种情况会更容易适应? 在100个不同的地方重新编写代码,还是在一个地方重新编写代码?
好吧...现在你说,在100个不同的地方重新编写它不是那么糟糕。
那么...现在你的客户说他需要在某些位置使用MySQL,在其他位置使用SQL Server,在另一些位置使用Oracle。
现在该怎么办?
在松散耦合的世界里,你可以有3个不同的数据库组件,它们都共享相同的接口,并具有不同的实现。在紧密耦合的世界中,你会有100个带有3种不同复杂度开关语句集的代码。

也许我对松耦合的理解有误。我认为,这意味着“不直接引用对象来执行特定任务,而是创建一个接口,我的对象可以实现该接口。这样,我可以以任何方式实现接口,而不会与之紧密耦合。”我不明白将其作为接口而不是直接类引用如何使其更易于适应。 - JLX
强类型不等于静态类型,请参见这里 - zoul
@Zoul... 不,强类型不等于静态类型,但是强类型等于编译器所强制执行的类型限制。 - Gerald
@JLX... 如果你对松耦合和紧耦合的介绍是通过阅读依赖注入相关内容,我可以理解你的困惑。依赖注入属于松耦合的极端范畴。但是,松耦合和紧耦合是相对的概念。当你“具体引用一个单一类”时,这比在代码中串联一堆低级数据库函数调用要松散。如果该具体类具有更通用的接口而不是特定于其内部实现,那么它仍然是更松散的。 - Gerald
@Gerald:也许这就是我的困惑所在。我一直认为松耦合只与 DI 有关,因为我见过 DI 的人将其作为该设计背后的主要原因之一。 - JLX
显示剩余4条评论

1
然而,我觉得强类型是被鼓励的,但松耦合是不好的。 我认为说强类型是好的或被鼓励的是不公平的。当然,许多人喜欢强类型语言,因为它带有编译时检查。但很多人会说弱类型也是好的。听起来你已经听说过“强”是好的,那么“松”怎么可能也是好的呢?一种语言的类型系统的优点甚至不在类设计的概念范畴之内。注意:不要混淆强类型和静态类型

我在 Stack Overflow 的 JavaScript 维基上注意到了“弱类型”这个术语,于是来到了这里。 “弱类型”这个术语对我来说有点不公平。它本质上是在做出判断,即弱类型是不好的,尽管在某些情况下它是有用的。我认为“松散类型”这个术语更加中立,即使与“强类型”并列有点奇怪。 - Eric

1

强类型可以帮助减少错误,同时通常有助于提高性能。代码生成工具可以收集有关变量可接受值范围的更多信息,这些工具可以生成更快的代码。

当与类型推断和特征(Perl6等)或类型类(Haskell)结合使用时,强类型代码仍然可以保持紧凑和优雅。


0

我认为紧密/松散耦合(对我来说:接口声明和对象实例的赋值)与Liskov原则有关。使用松散耦合可以获得Liskov原则的一些优势。

然而,一旦执行instanceof、cast或复制操作,使用松散耦合就开始变得可疑。此外,在方法或块内部的局部变量中使用是没有意义的。


0
如果在派生类中修改了我们的函数,这将改变基本抽象类中的代码,则表明存在完全依赖性,这意味着它是紧密耦合的。
如果我们不编写或重新编译代码,则显示出较少的依赖性,因此它是松散耦合的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接