什么是“松耦合”? 请提供示例

我似乎无法理解“松散耦合”的概念我认为“松散”这个词通常有一个负面的含义,所以我总是忘记松散耦合是 很好的东西。

是否有人可以展示一些“之前”和“之后”的代码(或伪代码)来说明这个概念?

123502 次浏览

定义

本质上,耦合就是给定对象或对象集在多大程度上依赖于另一个对象或另一组对象来完成其任务。

高耦合

想一辆车。为了让发动机启动,必须把钥匙插入点火装置,转动,必须有汽油,必须发生火花,活塞必须点火,发动机必须活过来。你可以说一个汽车引擎与其他几个物体是高度耦合的。这是高耦合,但这并不是一件坏事。

松耦合

想象一下网页的用户控件,它负责允许用户发布、编辑和查看某些类型的信息。单个控件可用于允许用户发布新的信息或编辑新的信息。控件应该能够在两个不同的路径(new 和 edit)之间共享。如果控件的编写方式使它需要来自包含它的页的某种类型的数据,那么可以说它的耦合度太高了。控件不应需要其容器页中的任何内容。

松耦合通常是两个参与者在相同的工作负载上相互独立地工作。因此,如果你有两个网络服务器使用相同的后端数据库,那么你会说,这些网络服务器是松散耦合。紧密耦合的例子是在一个 Web 服务器上有两个处理器... 这些处理器是紧密耦合的。

希望对你有所帮助。

对不起,但是“松耦合”不是一个编码问题,它是一个设计问题。“松耦合”这个术语与“高内聚力”的理想状态密切相关,它是相反的,但又是互补的。

松耦合意味着应该构造单个设计元素,以减少它们需要了解的关于其他设计元素的不必要信息的数量。

高内聚力有点像“紧密耦合”,但是高内聚力是这样一种状态,在这种状态下,真正需要了解彼此的设计元素被设计成能够干净而优雅地协同工作。

关键是,一些设计元素应该知道其他设计元素的细节,所以它们应该按照这种方式设计,而不是偶然的。其他设计元素不应该知道关于其他设计元素的细节,因此应该有目的地按照这种方式设计,而不是随机设计。

实现这一点留给读者作为练习:)。

紧密耦合的代码依赖于具体的实现。如果我的代码中需要一个字符串列表,并且我像这样声明它(在 Java 中)

ArrayList<String> myList = new ArrayList<String>();

那么我就依赖于数组列表的实现。

如果我想将其更改为松散耦合代码,我将引用设置为接口(或其他抽象)类型。

List<String> myList = new ArrayList<String>();

这阻止我在 myList上调用特定于 ArrayList 实现的 任何方法。我仅限于 List 接口中定义的那些方法。如果我后来决定我真的需要一个 LinkedList,我只需要改变我的代码在一个地方,我创建了新的 List,而不是在100个地方,我调用数组列表方法。

当然,您可以使用第一个声明实例化 ArrayList,并且限制自己不使用不属于 List 接口的任何方法,但是使用第二个声明会使编译器保持诚实。

您可以阅读更多关于 “松耦合”的一般概念。

简而言之,它描述了两个类之间的关系,在这种关系中,每个类对另一个类知道的最少,而且无论另一个类是否存在,每个类都可能继续正常工作,并且不依赖于另一个类的特定实现。

我将用 Java 作为例子,假设我们有一个这样的类:

public class ABC
{
public void doDiskAccess() {...}
}

当我给班级打电话的时候,我需要这样做:

ABC abc = new ABC();


abc. doDiskAccess();

到目前为止,一切都很好,现在让我们假设我有另一个类,看起来像这样:

public class XYZ
{
public void doNetworkAccess() {...}
}

它看起来和 ABC 完全一样,但是我们假设它是通过网络工作的,而不是在磁盘上。现在让我们写一个这样的程序:

if(config.isNetwork()) new XYZ().doNetworkAccess();
else new ABC().doDiskAccess();

这样可以,但是有点笨拙,我可以用这样的界面来简化它:

public interface Runnable
{
public void run();
}


public class ABC implements Runnable
{
public void run() {...}
}


public class XYZ implements Runnable
{
public void run() {...}
}

现在我的代码可以看起来像这样:

Runnable obj = config.isNetwork() ? new XYZ() : new ABC();


obj.run();

明白这有多简单明了了吧?我们刚刚理解了松耦合的第一个基本原则: 抽象。这里的关键是确保 ABC 和 XYZ 不依赖于调用它们的类的任何方法或变量。这使得 ABC 和 XYZ 成为完全独立的 API。或者换句话说,它们与父类是“解耦的”或“松散耦合的”。

但如果我们需要两者之间的沟通呢?那么,我们可以使用进一步的抽象,比如 事件模型,以确保父代码永远不需要与您创建的 API 耦合。

您可以将(紧密或松散)耦合理解为将一个特定类与其对另一个类的依赖分离所需的工作量。例如,如果类中的每个方法在底部都有一个小的 finally 块,您可以在其中调用 Log4Net 来记录某些内容,那么您可以说您的类与 Log4Net 是紧密耦合的。如果你的类包含一个名为 LogSomething 的私有方法,这是唯一一个调用 Log4Net 组件的地方(其他方法都被称为 LogSomething) ,那么你会说你的类与 Log4Net 是松散耦合的(因为它不需要花费太多的精力就可以把 Log4Net 拉出来并用其他东西替换它)。

考虑一个带有 FormA 和 FormB 的 Windows 应用程序。FormA 是主窗体,它显示 FormB。假设 FormB 需要将数据传递回其父节点。

如果你这样做:

class FormA
{
FormB fb = new FormB( this );


...
fb.Show();
}


class FormB
{
FormA parent;


public FormB( FormA parent )
{
this.parent = parent;
}
}

FormB 与 FormA 紧密耦合。 FormB 只能有 FormA 类型的父类。

另一方面,如果让 FormB 发布一个事件并让 FormA 订阅该事件,那么 FormB 可以通过该事件将数据推回到该事件的任何订阅者。在这种情况下,FormB 甚至不知道如何与其父进行对话; 通过松散耦合,事件提供的只是与订阅者的对话。任何类型现在都可以成为 FormA 的父类。

RP

这是一个非常普遍的概念,因此代码示例不会给出全部的情况。

一个同事对我说,“模式就像分形,当你放大非常近的时候,你可以看到它们,当你放大到架构级别的时候。”

阅读简短的维基百科页面可以让你了解这种普遍性:

Http://en.wikipedia.org/wiki/loose_coupling

至于具体的代码示例..。

下面是我最近处理的一个松散耦合,来自 Microsoft.Practices. CompositeUI。

    [ServiceDependency]
public ICustomizableGridService CustomizableGridService
{
protected get { return _customizableGridService; }
set { _customizableGridService = value; }
}

此代码声明此类对 CustomizableGridService 具有依赖项。它没有直接引用服务的确切实现,而是简单地指出它需要某些服务的实现。然后在运行时,系统解决这种依赖关系。

如果还不清楚,你可以在这里阅读更详细的解释:

Http://en.wikipedia.org/wiki/dependency_injection

假设 ABCCustomizableGridService 是我打算在这里挂接的实现。

如果我选择这样做,我可以将其取出,并用 XYZCustomizableGridService 或 StubCustomizableGridService 替换它,而对具有此依赖项的类不做任何更改。

如果我直接引用了 ABCCustomizableGridService,那么我需要对这个/那些引用/s 进行更改,以便在另一个服务实现中进行交换。

考虑一个简单的购物车应用程序,它使用一个 CartContents类来跟踪购物车中的商品,使用一个 Order类来处理购买。Order需要确定购物车中内容的总价值,它可以这样做:

紧密耦合的例子:

public class CartEntry
{
public float Price;
public int Quantity;
}


public class CartContents
{
public CartEntry[] items;
}


public class Order
{
private CartContents cart;
private float salesTax;


public Order(CartContents cart, float salesTax)
{
this.cart = cart;
this.salesTax = salesTax;
}


public float OrderTotal()
{
float cartTotal = 0;
for (int i = 0; i < cart.items.Length; i++)
{
cartTotal += cart.items[i].Price * cart.items[i].Quantity;
}
cartTotal += cartTotal*salesTax;
return cartTotal;
}
}

注意,OrderTotal方法(因此 Order 类)如何依赖于 CartContentsCartEntry类的实现细节。如果我们试图更改这个逻辑以允许折扣,我们可能必须更改所有3个类。此外,如果我们改为使用 List<CartEntry>集合来跟踪项目,我们还必须改变 Order类。

现在有一个更好的方法来做同样的事情:

减少耦合例子:

public class CartEntry
{
public float Price;
public int Quantity;


public float GetLineItemTotal()
{
return Price * Quantity;
}
}


public class CartContents
{
public CartEntry[] items;


public float GetCartItemsTotal()
{
float cartTotal = 0;
foreach (CartEntry item in items)
{
cartTotal += item.GetLineItemTotal();
}
return cartTotal;
}
}


public class Order
{
private CartContents cart;
private float salesTax;


public Order(CartContents cart, float salesTax)
{
this.cart = cart;
this.salesTax = salesTax;
}


public float OrderTotal()
{
return cart.GetCartItemsTotal() * (1.0f + salesTax);
}
}

特定于购物车行项目、购物车集合或订单的实现的逻辑仅限于该类。因此,我们可以更改这些类的任何实现,而不必更改其他类。我们可以通过改进设计、引入接口等方式进一步解耦,但我认为您明白这一点。

耦合与系统之间的依赖性有关,这些依赖性可以是代码模块(函数、文件或类)、管道中的工具、服务器-客户机进程等等。依赖关系越不一般,它们就变得越“紧密耦合”,因为更改一个系统需要更改依赖于它的其他系统。理想的情况是“松耦合”,其中一个系统可以更改,并且依赖于它的系统将继续工作而无需更改。

实现松散耦合的一般方法是通过定义良好的接口。如果两个系统之间的交互定义良好,并且双方都遵守,那么修改一个系统同时确保不打破约定就会变得更加容易。在实践中,通常没有建立定义良好的接口,导致设计草率和紧密耦合。

一些例子:

  • 应用程序依赖于库。在紧密耦合的情况下,应用程序会在 lib 的新版本上中断。谷歌“ DLL 地狱”。

  • 客户端应用程序从服务器读取数据。在紧耦合情况下,对服务器的更改需要在客户端进行修复。

  • 两个类在面向对象的层次结构中交互。在紧密耦合下,对一个类的更改需要更新另一个类以匹配。

  • 多个命令行工具在管道中通信。如果它们紧密耦合,对一个命令行工具版本的更改将导致读取其输出的工具出错。

这里的答案之间的差异程度表明了为什么这是一个很难理解的概念,但我可以简单地描述一下:

为了让我知道如果我把球扔给你,你就能接住它,我真的不需要知道你多大了。我不需要知道你早餐吃了什么,我真的不在乎你的第一个暗恋对象是谁。我只需要知道你能接住。如果我知道这个,那么我不在乎,如果是你,我扔球给你或你的兄弟。

使用非动态语言,如 c # 或 Java 等,我们通过接口实现这一点。假设我们有以下接口:

public ICatcher
{
public void Catch();
}

现在让我们说我们有以下的类:

public CatcherA : ICatcher
{
public void Catch()
{
console.writeline("You Caught it");
}


}
public CatcherB : ICatcher
{
public void Catch()
{
console.writeline("Your brother Caught it");
}


}

现在,CatcherACatcherB都实现了 Catch方法,因此需要捕捉器的服务可以使用这两种方法中的任何一种,而不必真正关心它是哪一种。因此,紧密耦合的服务可能直接实例化捕获的服务,即。

public CatchService
{
private CatcherA catcher = new CatcherA();


public void CatchService()
{
catcher.Catch();
}


}

因此,CatchService可以完全做它设定要做的事情,但是它使用 CatcherA,并且始终使用 CatcherA。它的硬编码,所以它停留在那里,直到有人出现,并重新构造它。

现在让我们采取另一种选择,称为依赖注入:

public CatchService
{
private ICatcher catcher;


public void CatchService(ICatcher catcher)
{
this.catcher = catcher;
catcher.Catch();
}
}

因此,实例化 CatchService的调用可以执行以下操作:

CatchService catchService = new CatchService(new CatcherA());

或者

CatchService catchService = new CatchService(new CatcherB());

这意味着 Catch服务不能与 CatcherACatcherB紧密耦合。

对于这样的松散耦合服务,还有其他一些策略,比如使用 IoC 框架等。

当两个组件依赖于彼此的具体实现时,它们是高度耦合的。

假设我在类的某个方法中有这段代码:

this.some_object = new SomeObject();

现在我的类依赖于 Some Object,它们是高度耦合的。另一方面,假设我有一个方法 InjectSome Object:

void InjectSomeObject(ISomeObject so) { // note we require an interface, not concrete implementation
this.some_object = so;
}

然后,第一个示例可以只使用注入的 Some Object。通过正常操作,您可以使用繁重的、使用数据库的、使用网络的类等,同时通过轻量级的模拟实现进行测试。使用紧密耦合的代码是做不到这一点的。

通过使用依赖注入容器,你可以使这项工作的某些部分变得更容易。你可以在 Wikipedia: http://en.wikipedia.org/wiki/Dependency_injection上了解更多关于 DI 的信息。

有时候做得太过火很容易。在某种程度上,你必须让事情变得具体,否则你的程序将不那么可读和易懂。因此,主要在组件边界使用这种技术,并了解您正在做什么。确保您正在利用松散耦合的优势。如果没有,你可能不需要它在那个地方。DI 可能会使您的程序更加复杂。确保你做了一个很好的权衡。换句话说,保持良好的平衡。祝你好运!

在计算机科学中,“松耦合”还有另外一个意思,这里没有其他人发表过,所以... ... 这里是——希望你能给我一些投票,这样就不会在堆的底部丢失!当然,我的回答的主题属于对这个问题的任何全面的回答... 也就是说:

术语“松耦合”最初作为一个形容词进入计算领域,用于描述多 CPU 配置中的 CPU 体系结构。它的对应术语是“紧密耦合”。松耦合是指 CPU 不共享许多公共资源,而紧耦合是指 CPU 共享许多公共资源。

术语“系统”在这里可能会混淆,所以请仔细分析这种情况。

通常(但并非总是如此) ,硬件配置中的多个 CPU 在一个系统中(如在单个“ PC”机器中)将紧密耦合。除了一些超高性能系统的子系统实际上跨“系统”共享主内存之外,所有可分离的系统都是松散耦合的。

紧耦合和松耦合这两个术语是 之前多线程和多核 CPU 发明的,所以这些术语可能需要一些伙伴来充分表达当今的情况。事实上,今天我们很可能拥有一个将两种类型都包含在一个整体系统中的系统。关于当前的软件系统,有两种常见的体系结构,每种体系结构都有一种,这些体系结构足够常见,应该是我们所熟悉的。

首先,因为这就是问题所在,一些松耦合系统的例子:

  • VaxClusters
  • Linux 集群

相比之下,一些紧密耦合的例子:

  • 半导体-多处理(SMP)操作系统-例如 Fedora 9
  • 多线程 CPU
  • 多核 CPU

在今天的计算中,在一个整体系统中同时进行两种操作的例子并不少见。例如,以运行 Fedora 9的现代奔腾双核或四核 CPU 为例,它们是紧密耦合的计算系统。然后,将它们中的一些组合到一个松散耦合的 Linux 集群中,现在就可以进行松散和紧密耦合的计算了!现代硬件真是太棒了!

这里有一些很长的答案。原则很简单。我提交 维基百科的开场白:

”松耦合描述了两个或多个具有某种交换关系的系统或组织之间的弹性关系。

交易的每一端都明确提出了自己的要求,而对另一端几乎没有任何假设。”

许多集成产品(尤其是苹果公司的产品) ,如 IPodIPad是一个很好的紧密耦合的例子: 一旦电池耗尽,你不妨购买一个新的设备,因为电池是固定的,不会松动,从而使更换非常昂贵。一个松散耦合的玩家可以毫不费力地更换电池。

同样的 也适用于软件开发: 一般来说,使用松散耦合的代码来促进扩展和替换(并使单个部件更容易理解)会更好。但是,在特殊情况下,紧密耦合可能是有利的,因为几个模块的紧密集成允许更好的优化。

我提出一个非常简单的 码耦合测试:

  1. 如果存在任何可能的对代码段 B 的修改,那么代码段 A 将与代码段 B 紧密耦合,从而迫使对代码段 A 的修改以保持正确性。

  2. 如果没有可能对代码段 B 进行修改,从而使对代码段 A 的修改成为必要,则代码段 A 不会与代码段 B 紧密耦合。

这将帮助您验证代码片段之间的耦合程度。 关于这个问题的推理,可以看看这篇博客文章: 《 http://marekdec.wordpress.com/2012/11/14/loose-coupling-tight-coupling-decoupling-what-is-that-all-about/》

在简单语言中,松耦合意味着它不依赖于其他事件的发生,而是独立执行。

当您在其他类中使用 new关键字创建一个类的对象时,您实际上是在进行紧耦合(糟糕的做法) ,相反,您应该使用松耦合,这是一种好的做法

- 咖啡

package interface_package.loose_coupling;


public class A {


void display(InterfaceClass obji)
{
obji.display();
System.out.println(obji.getVar());
}
}

- 咖啡

package interface_package.loose_coupling;


public class B implements InterfaceClass{


private String var="variable Interface";


public String getVar() {
return var;
}


public void setVar(String var) {
this.var = var;
}


@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("Display Method Called");
}
}

- 接口类

package interface_package.loose_coupling;


public interface InterfaceClass {


void display();
String getVar();
}

主流

package interface_package.loose_coupling;


public class MainClass {


public static void main(String[] args) {
// TODO Auto-generated method stub


A obja=new A();
B objb=new B();
obja.display(objb);     //Calling display of A class with object of B class
    

}
}

说明:

在上面的例子中,我们有两个类 A 和 B

类 B 实现接口,即 InterfaceClass。

InterfaceClass 为 B 类定义了一个契约,因为 InterfaceClass 具有 B 类的抽象方法,任何其他类(例如 A)都可以访问这些方法。

在类 A 中,我们有一个显示方法,它可以除了实现 InterfaceClass 的类的对象(在我们的例子中是 B 类)。在类 A 的对象方法上调用类 B 的 display ()和 getVar ()

在 MainClass 中,我们创建了类 A 和类 B 的对象,并通过传递类 B 的对象即 obb 来调用 A 的显示方法。用 B 类对象调用 A 的显示方法。

现在说到松耦合。假设将来你必须将类 B 的名称改为 ABC,那么你就不必在类 B 的显示方法中改变它的名称,只需创建 new (ABC 类)的对象并将其传递给 MailClass 中的 display 方法。你不需要改变 A 类的任何东西

档号: https://speckyfox.com/blog/5-steps-to-loose-coupling-in-java-using-interfaces

耦合指的是不同类之间相互连接的紧密程度。紧密耦合类包含大量的交互和依赖关系。

松散耦合类正好相反,因为它们彼此之间的依赖性被保持在最低限度,而是依赖于彼此之间定义良好的公共接口。

乐高,玩具 SNAP 一起将被认为是松散耦合,因为你可以只是折断件一起,并建立任何系统,你想要的。然而,拼图游戏有紧密耦合的部分。你不能从一个拼图游戏(系统)中取出一块拼图,然后把它拼接成另一个拼图,因为这个系统(系统)非常依赖于那个特定“设计”所特有的非常具体的拼图。乐高是建在一个更通用的时尚,使他们可以用在你的乐高屋,或在我的乐高外星人。

参考资料: https://megocode3.wordpress.com/2008/02/14/coupling-and-cohesion/