能否描述面向对象范式中松耦合和紧耦合的确切区别?
紧耦合是指一组类高度依赖于彼此。
当一个类承担过多职责或一个关注点分散在多个类中而不是有自己的类时,就会出现这种情况。
松耦合是通过促进单一责任和关注点分离的设计来实现的。
一个松耦合的类可以独立于其他(具体)类进行使用和测试。
接口是用于解耦的强大工具。类可以通过接口而不是其他具体类进行通信,任何类都可以通过实现该接口来成为该通信的另一端。
紧耦合的示例:
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)
{
}
}
另一个例子在这里。
用简单的类比来解释这个概念。代码可以稍后再来。
我不得不重新编写整个程序。但这一次,我使用了接口 - 一种松耦合的设计模式。现在,添加新的输出格式变得更加容易。我可以编辑 JSON 部分而不担心会破坏 CSV 输出。
DB示例:如果你想要轻松地从sqlLite切换到PostGreSQL,松耦合的代码使得这一切变得非常容易(就像穿上红色衬衫而不是蓝色衬衫一样)。Rails ActiveRecord库在其数据库实现上是松耦合的。这使得某人可以很容易地使用自己的数据库实现,同时使用相同的代码基础!
云服务提供商示例:或者,如果你正在使用AWS,并且由于市场主导地位而开始收费过高,你应该能够相对容易地切换到Google或Azure等。这正是Active Storage等库存在的原因 - 它们为用户提供了对所使用的特定云服务提供商的健康冷漠态度(如Azure、AWS S3、GCS等)。你只需更改一行代码就可以轻松更换云服务提供商。云存储提供商的实现细节是松耦合的。
测试:如果你想要测试你的软件,使用预定的输出和输入 - 你将如何做到?通过松耦合的软件 - 这是轻而易举的:你可以运行你的测试,也可以部署你的生产代码,所有这些都可以在同一个代码基础上完成。而对于紧密耦合的代码来说,测试生产代码几乎是不可能的。
我们需要使一切都"松耦合"吗?可能不需要。我们应该行使判断力。我还建议不要猜测事物将会发生什么变化。只有在需要的时候才进行松耦合。
简而言之,松耦合使代码更易于变更。
上面的答案提供了一些值得阅读的代码。
高级主题
松耦合与多态和接口密切相关。如果你喜欢卡通和类比,请考虑阅读我写过的其他帖子:
脚注:
** 以流行音乐启发的示例。感谢MJ。
图片来源。
在面向对象设计中,耦合度的多少是指一个类的设计对另一个类的设计依赖程度。换句话说,A 类的更改会不会经常导致 B 类的相关变化?紧密耦合意味着两个类经常一起变化,松散耦合意味着它们大多是独立的。通常推荐使用松散耦合,因为易于测试和维护。
您可能会发现Martin Fowler 的这篇论文 (PDF)有所帮助。
紧耦合是指一个类依赖于另一个类。
松耦合是指一个类依赖于接口而非类。
在紧耦合中,方法中声明了硬编码的依赖关系。
在松耦合中,我们必须在运行时外部传递依赖关系,而不是硬编码。(松耦合系统使用接口减少与类之间的依赖关系。)
例如,我们有一个可以以两种或多种方式发送输出的系统,如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();
}
}
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();
}
}
一般来说,紧耦合通常是不好的,因为它降低了代码的灵活性和可重用性,使更改变得更加困难,妨碍了可测试性等。
紧密耦合的对象需要相互了解,并且通常高度依赖于彼此的接口。在紧密耦合的应用程序中更改一个对象通常需要更改其他多个对象,在小型应用程序中我们可以轻松识别这些更改,而且很少会遗漏任何内容。但在大型应用程序中,这些相互依赖关系并非每个程序员都知道或可能会错过更改。而每组松散耦合的对象都不依赖于其他对象。
简而言之,松散耦合是一种设计目标,旨在减少系统组件间的相互依赖性,从而降低一个组件的更改对其他任何组件的影响风险。松散耦合是一个更通用的概念,旨在增加系统的灵活性,使其更易维护,并使整个框架更加“稳定”。
耦合指一个元素直接了解另一个元素的程度。例如:A和B,只有当A改变其行为时,B才会改变其行为。松散耦合的系统可以轻松地分解成可定义的元素。
松耦合表示两个组件之间的依赖程度非常低。
例如: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()
方法为我们提供了当前购物车商品的完整金额。如果我们想要在这个购物车系统中添加折扣功能,由于代码之间紧密耦合,很难在不改变每个类的情况下做到这一点。