任何人都可以描述面向对象范式中松耦合和紧耦合之间的确切区别吗?
16 回答
紧耦合是指一组类相互高度依赖。
当一个类承担了太多责任,或者当一个关注点分布在许多类而不是拥有自己的类时,就会出现这种情况。
松散耦合是通过促进单一职责和关注点分离的设计实现的。
松散耦合的类可以独立于其他(具体)类来使用和测试。
接口是用于解耦的强大工具。类可以通过接口而不是其他具体类进行通信,并且任何类都可以在通信的另一端,只需实现接口即可。
紧耦合示例:
class CustomerRepository
{
private readonly Database database;
public CustomerRepository(Database database)
{
this.database = database;
}
public void Add(string CustomerName)
{
database.AddRow("Customer", CustomerName);
}
}
class Database
{
public void AddRow(string Table, string Value)
{
}
}
松耦合的例子:
class CustomerRepository
{
private readonly IDatabase database;
public CustomerRepository(IDatabase database)
{
this.database = database;
}
public void Add(string CustomerName)
{
database.AddRow("Customer", CustomerName);
}
}
interface IDatabase
{
void AddRow(string Table, string Value);
}
class Database implements IDatabase
{
public void AddRow(string Table, string Value)
{
}
}
另一个例子在这里。
无代码说明
最好从简单的类比开始:
松散耦合的总结示例:
在上图中,帽子与身体“松散耦合”。这意味着您可以轻松摘下帽子,而无需对人/身体进行任何更改。当你能做到这一点时,你就有了“松散耦合”。详见下文。
紧耦合(详细示例)
想想你的皮肤。它粘在你的身体上。它像手套一样合身。但是,如果您想将肤色从白色变为黑色怎么办?你能想象剥下你的皮肤,染色,然后再贴回去等会是多么痛苦吗?改变你的皮肤很困难,因为它与你的身体紧密相连。你不能轻易做出改变。你必须从根本上重新设计一个人才能使这成为可能。
- 关键点#1:换句话说,如果你想改变皮肤,你也必须改变你的身体设计,因为两者是结合在一起的——它们是紧密结合的。
上帝不是一个优秀的面向对象程序员。
松散耦合(详细示例)
现在想想早上穿衣服。你不喜欢蓝色?没问题:你可以换上一件红衬衫。您可以轻松轻松地做到这一点,因为衬衫与您的身体的连接方式与您的皮肤不同。这件衬衫不知道也不关心它正在发生什么身体。换句话说,你可以改变你的衣服,而不用真正改变你的身体。
- 这是关键点#2。如果你改变你的衬衫,那么你就不会被迫改变你的身体——当你能做到这一点时,你就有了松散的耦合。当你不能这样做时,你就有了紧密的耦合。
简而言之,这就是基本概念。
为什么这一切都很重要?
编写软件时,变化是不可避免的。如果我们事先知道某个特定位置将发生更改,那么我们应该确保我们的软件在该特定点上是松散耦合的,因为这将使我们能够轻松快速地进行这些更改,而不会出现错误...... . 那是什么意思?考虑一些例子:
软件中的松散耦合:
- CSV/JSON 示例:在我职业生涯的早期,我的经理说:“将输出作为 CSV 文件给我”。伟大的。我破解并创建了一个像魅力一样工作的例程。然后一两周后,他说:“实际上,我想要另一个客户端的 JSON 输出”。
“你想要一个 CSV 格式的输出,另一个是 JSON 格式的输出吗?”
“这是正确的”
多么痛苦。我不得不重写整个事情。谁知道?明天他可以转身说:“我想要 XML 格式的”。
我应该知道他会要求不同的格式,我本可以计划的。
我使用接口重写了整个东西 - 一种松散耦合的设计模式 - 现在添加新的输出格式或更改内容现在变得更加容易。我可以编辑 JSON 部分而不必担心会破坏我的 CSV 输出。
DB 示例:如果您想轻松地从 MySQL 切换到 PostGreSQL - 松散耦合的代码使切换变得非常容易(即穿上红色衬衫而不是蓝色衬衫)。Rails ActiveRecord 库与其数据库实现松散耦合。这使得人们在使用相同的代码库的同时使用自己的数据库实现变得超级容易!
云提供商示例:或者,如果您使用 AWS,并且由于市场主导地位而开始收费过高,您应该能够轻松切换到 Google 或 Azure 等。这正是 Active Storage 等库存在的原因——它们提供对正在使用的特定云提供商(Azure、AWS S3、GCS 等)完全漠不关心的用户。您只需更改一行代码即可轻松更改云提供商。云存储提供商的实现细节是松耦合的。
测试:如果你想用预先确定的输出和输入来测试你的软件——你打算怎么做?使用松散耦合的软件 - 轻而易举:您可以运行测试,还可以部署生产代码并在同一个代码库中完成所有工作。使用紧密耦合的代码,几乎不可能测试您的生产代码。
我们是否需要让一切都“松散耦合”?可能不是。我们必须运用我们的判断力。在编写代码时,一定程度的耦合是不可避免的。但是,如果可以的话,您应该考虑将其最小化,特别是如果您事先知道它会在哪里发生变化。
概括
简而言之,松散耦合使代码更容易更改。
上面的答案提供了一些值得一读的代码。
高级主题
松散耦合与多态性和接口密切相关。如果您想继续阅读一些更高级的主题,这里有一些其他的堆栈溢出答案可能会有所帮助:
(如果我的答案令人困惑 - 请发表评论以便我修复。@lupchiazoem 在下面写了一个非常精彩的回复,也值得一读。)
在面向对象的设计中,耦合量是指一个类的设计在多大程度上依赖于另一个类的设计。换句话说,A 类的变化多久会迫使 B 类发生相关的变化?紧耦合意味着两个类经常一起变化,松耦合意味着它们大部分是独立的。一般来说,推荐使用松散耦合,因为它更容易测试和维护。
您可能会发现Martin Fowler 的这篇论文 (PDF)很有帮助。
一般来说,紧耦合在大多数情况下都是不好的,因为它降低了代码的灵活性和可重用性,使更改变得更加困难,阻碍了可测试性等。
紧耦合对象是一个需要相互了解很多并且通常高度依赖于彼此的接口的对象。在紧密耦合的应用程序中更改一个对象通常需要更改许多其他对象,在小型应用程序中我们可以轻松识别更改,并且错过任何内容的机会更少。但是在大型应用程序中,并非每个程序员都知道这些相互依赖关系,否则就有可能错过更改。但是每组松散耦合的对象都不依赖于其他对象。
简而言之,我们可以说,松散耦合是一种设计目标,旨在减少系统组件之间的相互依赖关系,目的是降低一个组件的更改需要任何其他组件的更改的风险。松散耦合是一个更通用的概念,旨在增加系统的灵活性,使其更易于维护,并使整个框架更“稳定”。
耦合是指一个元素对另一个元素的直接了解程度。我们可以说一个 eg:A 和 B,只有当 A 改变它的行为时,只有 B 改变它的行为。松散耦合的系统可以很容易地分解为可定义的元素。
紧耦合意味着一个类依赖于另一个类。
松散耦合意味着一个类依赖于接口而不是类。
在紧密耦合中,方法中声明了硬编码的依赖关系。
在松耦合中,我们必须在运行时外部传递依赖而不是硬编码。(松散耦合系统使用接口来减少对类的依赖。)
例如,我们有一个系统可以通过 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();
}
}
松耦合意味着两个组件之间的依赖程度非常低。
示例:GSM SIM
紧耦合意味着两个组件之间的依赖程度非常高。
示例:CDMA 移动
摘自我关于耦合的博客文章:
什么是紧耦合:-
正如上面定义的那样,紧耦合对象是一个需要了解其他对象并且通常高度依赖于彼此接口的对象。
当我们在紧密耦合的应用程序中更改一个对象时,通常需要更改许多其他对象。在一个小的应用程序中没有问题我们可以很容易地识别出变化。但在大型应用程序的情况下,并非每个消费者或其他开发人员都知道这些相互依赖关系,或者未来有很多变化的机会。
让我们通过一个购物车演示代码来理解紧耦合:
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# 中完成的呢?接口和依赖注入!
编辑
这也是装饰器模式的一个很好的例子,眼睛是我们通过满足界面要求来装饰的类,但提供了不同的功能(例如太阳镜、老花镜、珠宝商的放大镜等)
有一些工具通过他们的库提供依赖注入,例如在 .net 中我们有ninject Library。
如果你在 java 中走得更远,那么spring提供了这个功能。
可以通过在代码中引入接口来制作松散耦合的对象,这就是这些源所做的。
在你的代码中说你正在编写
Myclass m = new Myclass();
现在你的方法中的这个声明说你依赖myclass
这个被称为紧密耦合。现在您提供一些构造函数注入或属性注入和实例化对象,那么它将变得松散耦合。
这是关于类对另一个类的依赖率,在松散耦合中如此之低,而在紧密耦合中如此之高。在面向服务的体系结构中要明确,服务之间是松散耦合的,而不是单一的,哪些类相互依赖是故意的。
松散耦合是在不提供依赖项的所有信息(即在接口的 from 中)的情况下间接提供您的类需要的依赖项的过程,以防您直接在依赖项中提供紧密耦合,这不是一种好的编码方式。
如果一个对象的创建/存在依赖于另一个无法定制的对象,则它的紧密耦合。而且,如果可以定制依赖关系,它的松散耦合。考虑一个 Java 示例:
class Car {
private Engine engine = new Engine( "X_COMPANY" ); // this car is being created with "X_COMPANY" engine
// Other parts
public Car() {
// implemenation
}
}
类的客户端Car
可以创建一个只有“X_COMPANY”引擎。
考虑打破这种耦合以改变它:
class Car {
private Engine engine;
// Other members
public Car( Engine engine ) { // this car can be created with any Engine type
this.engine = engine;
}
}
现在,aCar
不依赖于“X_COMPANY”的引擎,因为它可以使用类型创建。
Java 特定注意事项:仅出于解耦目的使用 Java 接口不是正确的设计方法。在 Java 中,接口有一个目的——充当契约,它在本质上提供解耦行为/优势。
Bill Rosmus 在接受的答案中的评论有一个很好的解释。
松紧耦合是关于一个程序组件与另一个程序组件的依赖关系。这意味着不仅依赖于编程类,它还与编程系统组件有关。
例如,如果您只使用简单的原始 SQL 查询从 SQL Server 接收数据,这就是松散耦合。与松散耦合和简单的原始 SQL 查询相反的是紧耦合和实体框架核心。在 Entity Framework Core 中,您必须在代码中使用 POCO 类的完整模型来反映数据库结构,这意味着数据库中的任何更改都必须反映在代码中。
因此,程序代码和数据库结构之间的紧密耦合是实体框架,这种方法的反面是拒绝使用任何 ORM 并拒绝在程序代码中拥有完整的镜像数据库结构。