在面向对象范式中,有人能准确地描述松耦合和紧耦合之间的区别吗?
当前回答
无代码说明
用简单的类比来解释概念。代码可以稍后再写。
松耦合的例子:
在上图中,帽子与身体“松散耦合”。这意味着你可以很容易地摘下帽子,而不需要对人/身体做任何改变。当你能做到这一点时,你就有了“松耦合”。详情见下文。
详细的例子
想想你的皮肤。它粘在你身上了。它非常合适。但是如果你想把你的肤色从白色变成黑色呢?你能想象剥掉你的皮肤,染色,然后再贴回去有多痛苦吗?改变你的皮肤是困难的,因为它与你的身体紧密相连。你只是不能轻易做出改变。为了使这成为可能,你必须从根本上重新设计一个人。
关键点#1:换句话说,如果你想改变皮肤,你也必须改变你身体的设计,因为两者是连接在一起的——它们是紧密耦合的。
上帝不是一个优秀的面向对象程序员。
松耦合(详细示例)
现在想想早上穿衣服。你不喜欢蓝色?没问题:你可以换一件红衬衫。你可以轻松轻松地做到这一点,因为衬衫并不像皮肤那样真正地连接在你的身体上。衬衫不知道也不关心它穿在什么身体上。换句话说,你可以改变你的衣服,而不需要真正改变你的身体。
这是第二点。如果你换了衬衫,那么你就不会被迫改变你的身体——当你可以这样做时,你就有了松耦合。当你不能这样做时,你就有了紧密耦合。
这是一个简单的基本概念。
为什么所有这些都很重要?
在编写软件时,更改是不可避免的。如果我们提前知道变更将发生在某个特定的地方,那么我们应该确保我们的软件在那个特定的点上是松散耦合的,因为这将使我们能够轻松快速地进行这些更改,而没有错误.....这意味着什么呢?看一些例子:
软件中的松耦合:
CSV/JSON示例:在我职业生涯的早期,我的经理说:“给我一个CSV文件的输出”。太好了。我开始努力,创造了一个像魔法一样有效的日常工作。一两周后,他说:“实际上,我想为另一个客户端提供JSON格式的输出。”
真痛苦。我不得不重写整个剧本。我有点知道会发生这种情况,所以我用接口重写了整个程序——一种松散耦合的设计模式,现在,添加了新的输出格式,进行更改就容易多了。我可以编辑JSON部分,而不用担心我会破坏我的CSV输出。
另外重要的一点是:软件的变化是正常的。在变化点上松散耦合。
DB Examples: if you want to switch from sqlLite to PostGreSQL easily - loosely coupled code makes it really easy to switch (i.e. to put on a red shirt instead of a blue shirt). The Rails ActiveRecord library is loosely coupled on its database implementation. This makes it super easy for someone to use their own database implementation, while using the same code base! Cloud Provider examples: Or if you're using AWS and they start charging too much because of market dominance, you should be able to somewhat easily switch to Google or Azure etc. This is precisely the reason why libraries like Active Storage exist - they provide users with a healthy indifference as to the specific cloud provider being used (Azure, AWS S3, GCS etc.). You can easily change cloud providers with just a one-line code change. The implementation details of the cloud storage providers are loosely coupled. Testing: if you want to test your software, with predetermined outputs and inputs - how are you going to do it? With loosely coupled software - it's a breeze: you can run your tests, and you can also deploy your production code and do it all in the same code base. With tightly coupled code, testing your production code is nearly impossible.
我们是否需要让所有内容都“松散耦合”?可能不会。我们必须运用我们的判断力。一定程度的耦合是不可避免的。但是如果你提前知道它会在哪里发生变化,就考虑最小化它。我还建议不要猜测事情会在哪里发生变化,不要将所有事情松散耦合。松散的情侣,只在你需要的时候。
总结
简而言之,松耦合使代码更容易更改。
上面的答案提供了一些值得一读的代码。
高级的主题
松耦合与多态性和接口密切相关。如果你喜欢漫画和类比,看看我写过的其他文章:
什么是多态性? 什么是接口? 你说的“漏洞百出的抽象”是什么意思——不是我写的。
归因。
其他回答
这是关于类对其他类的依赖率,在松散耦合时很低,而在紧密耦合时很高。在面向服务的体系结构中,服务之间是松散耦合的,不同的类之间的依赖是有意的
我的理解是,与松散耦合的体系结构相比,紧密耦合的体系结构并没有为更改提供很多灵活性。
但是对于松散耦合的体系结构,消息格式或操作平台或修改业务逻辑不会影响另一端。如果系统因改造而关闭,当然另一端在一段时间内不能访问服务,但除此之外,未更改的一端可以恢复消息交换,就像改造之前一样。
紧密耦合意味着一个类依赖于另一个类。 松耦合意味着一个类依赖于接口而不是类。
在紧密耦合中,方法中声明了硬编码的依赖项。 在松耦合中,我们必须在运行时向外部传递依赖项,而不是硬编码。(松散耦合系统使用接口来减少与类的依赖。)
例如,我们有一个系统可以以两种或多种方式发送输出,如JSON输出、CSV输出等。
紧耦合的
public interface OutputGenerator {
public void generateOutput();
}
public class CSVOutputGenerator implements OutputGenerator {
public void generateOutput() {
System.out.println("CSV Output Generator");
}
}
public class JSONOutputGenerator implements OutputGenerator {
public void generateOutput() {
System.out.println("JSON Output Generator");
}
}
// In Other Code, we write Output Generator like...
public class Class1 {
public void generateOutput() {
// Here Output will be in CSV-Format, because of hard-coded code.
// This method tightly coupled with CSVOutputGenerator class, if we want another Output, we must change this method.
// Any method, that calls Class1's generateOutput will return CSVOutput, because Class1 is tight couple with CSVOutputGenerator.
OutputGenerator outputGenerator = new CSVOutputGenerator();
output.generateOutput();
}
}
在上面的例子中,如果我们想要更改JSON中的输出,那么我们需要在整个代码中找到并更改,因为Class1与CSVOutputGenerator类紧密耦合。
松散耦合
public interface OutputGenerator {
public void generateOutput();
}
public class CSVOutputGenerator implements OutputGenerator {
public void generateOutput() {
System.out.println("CSV Output Generator");
}
}
public class JSONOutputGenerator implements OutputGenerator {
public void generateOutput() {
System.out.println("JSON Output Generator");
}
}
// In Other Code, we write Output Generator like...
public class Class1 {
public void generateOutput(OutputGenerator outputGenerator) {
// if you want to write JSON, pass object of JSONOutputGenerator (Dependency will be passed externally to this method)
// if you want to write CSV, pass object of CSVOutputGenerator (Dependency will be passed externally to this method)
// Due to loose couple with class, we don't need to change code of Class1, because Class1 is loose coupled with CSVOutputGenerator or JSONOutputGenerator class
// Any method, that calls Class1's generateOutput will desired output, because Class1 does not tight couple with CSVOutputGenerator or JSONOutputGenerator class
OutputGenerator outputGenerator = outputGenerator;
output.generateOutput();
}
}
摘自我关于耦合的博客文章:
什么是紧密耦合:-
正如上面定义的那样,紧密耦合对象是一个需要了解其他对象的对象,并且通常高度依赖于彼此的接口。
当我们更改紧耦合应用程序中的一个对象时,通常需要更改许多其他对象。在一个小的应用程序中没有问题,我们可以很容易地识别变化。但在大型应用程序中,这些相互依赖关系并不总是为每个消费者或其他开发人员所了解,或者将来可能会有很多变化。
让我们用购物车演示代码来理解紧耦合:
namespace DNSLooseCoupling
{
public class ShoppingCart
{
public float Price;
public int Quantity;
public float GetRowItemTotal()
{
return Price * Quantity;
}
}
public class ShoppingCartContents
{
public ShoppingCart[] items;
public float GetCartItemsTotal()
{
float cartTotal = 0;
foreach (ShoppingCart item in items)
{
cartTotal += item.GetRowItemTotal();
}
return cartTotal;
}
}
public class Order
{
private ShoppingCartContents cart;
private float salesTax;
public Order(ShoppingCartContents cart, float salesTax)
{
this.cart = cart;
this.salesTax = salesTax;
}
public float OrderTotal()
{
return cart.GetCartItemsTotal() * (2.0f + salesTax);
}
}
}
上面例子中的问题
紧密耦合产生了一些困难。
在这里,OrderTotal()方法为我们提供了购物车当前项目的完整金额。如果我们想在这个购物车系统中添加折扣功能。在上面的代码中很难做到这一点,因为我们必须在每个类中进行更改,因为它是非常紧密耦合的。
这里有很多使用类比的很好的答案,但是一个工作中的朋友给了我一个例子,比这里提到的所有例子都更让我喜欢……眼睛和眼镜!
紧密耦合
紧密耦合是眼睛。如果我想修复我的视力,做眼部移植手术非常昂贵,而且有相当大的风险。但如果设计师(作为人类)找到了更好的方法呢?添加一个与主体松散耦合的特性,这样就可以轻松地更改它!(是的. .眼镜)
松散耦合
我可以很容易地更换我的眼镜,而不会破坏我的潜在视力。我可以摘下眼镜,我的视力就会恢复到以前的水平(不是更好或更差)。使用不同的眼镜改变了我们通过眼睛看世界的方式,而且风险很小,易于维护。
总结
所以下次有人问你“谁在乎我的代码是否紧密耦合?”答案都是关于努力改变,努力维持和改变的风险。
那么这在c#中是如何实现的呢?接口和依赖注入!
EDIT
这也是Decorator模式的一个很好的例子,其中眼睛是我们通过满足界面需求来装饰的类,但提供了不同的功能(例如太阳镜、老花镜、珠宝商的放大镜等)。