183

我似乎无法理解“松散耦合”的概念。我想“松散”这个词通常带有负面含义并没有帮助,所以我总是忘记松散耦合是一件好事

有人会展示一些说明这个概念的“之前”和“之后”代码(或伪代码)吗?

4

20 回答 20

188

考虑一个简单的购物车应用程序,它使用一个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);
    }
}

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

于 2008-10-22T20:04:25.270 回答
166

许多集成产品(尤其是苹果公司的),如iPodiPad是紧密耦合的一个很好的例子:一旦电池没电了,你还不如买一个新设备,因为电池是焊接固定的,不会松动,因此更换非常方便昂贵的。松散耦合的播放器将允许轻松更换电池。

软件开发也是如此:松散耦合的代码通常(很多)更好,以便于扩展和替换(并使各个部分更容易理解)。但是,在极少数情况下,在特殊情况下紧密耦合可能是有利的,因为几个模块的紧密集成可以实现更好的优化。

于 2008-12-31T13:41:20.903 回答
65

我将使用 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 耦合。

于 2008-10-22T18:35:53.153 回答
37

抱歉,“松散耦合”不是编码问题,而是设计问题。术语“松散耦合”与“高内聚”的理想状态密切相关,相反但互补。

松散耦合仅仅意味着应该构建单独的设计元素,以便减少他们需要了解的关于其他设计元素的不必要信息量。

高内聚有点像“紧耦合”,但高内聚是一种状态,其中真正需要相互了解的设计元素被设计成能够干净优雅地协同工作。

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

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

于 2008-10-22T18:30:12.887 回答
34

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

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

然后我依赖于 ArrayList 实现。

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

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

这使我无法调用特定于 ArrayList 实现的任何方法。myList我仅限于 List 接口中定义的那些方法。如果我稍后决定我真的需要一个 LinkedList,我只需要在我创建新 List 的一个地方更改我的代码,而不是在我调用 ArrayList 方法的 100 个地方更改我的代码。

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

于 2008-10-22T18:32:21.697 回答
31

这里答案之间的差异程度说明了为什么它是一个难以掌握的概念,但要尽可能简单地描述它:

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

对于像 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");
   }

}

现在两者都CatcherA实现CatcherBCatch方法,因此需要 Catcher 的服务可以使用其中任何一个,而不必真正在乎它是哪一个。所以一个紧密耦合的服务可能会直接实例化一个被捕获的 ie

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

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

}

因此,CatchService可能会完全按照它的计划去做,但它会使用CatcherA并将始终使用 user 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服务没有与CatcherA或紧密耦合CatcherB

像这样的松散耦合服务还有其他几种策略,例如使用 IoC 框架等。

于 2008-10-22T23:50:58.807 回答
23

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

于 2008-10-22T18:36:28.333 回答
14

定义

本质上,耦合是给定对象或一组对象依赖另一个对象或另一组对象以完成其任务的程度。

高耦合

想一辆车。为了使发动机启动,必须​​将钥匙插入点火装置,转动,必须存在汽油,必须产生火花,必须点火,并且必须启动发动机。您可以说汽车引擎与其他几个对象高度耦合。这是高耦合,但这并不是一件坏事。

松耦合

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

于 2008-10-22T18:25:31.460 回答
7

这是一个非常笼统的概念,因此代码示例并不能说明全部情况。

一位工作人员对我说,“模式就像分形,当你放大到非常接近时,你可以看到它们,当你缩小到架构级别时,你可以看到它们。”

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

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 以便交换另一个服务实现。

于 2008-10-22T18:39:08.157 回答
7

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

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

一些例子:

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

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

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

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

于 2008-10-22T23:20:07.823 回答
5

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

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

乐高积木,SNAP 在一起的玩具将被认为是松散耦合的,因为您可以将零件拼在一起并构建您想要的任何系统。然而,一个拼图游戏的部分是紧密耦合的。你不能从一个拼图(系统)中取出一块拼图,然后把它拼成不同的拼图,因为系统(拼图)非常依赖于为特定“设计”而构建的非常具体的拼图。乐高积木以更通用的方式构建,因此可以在您的乐高屋或我的乐高外星人中使用。

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

于 2016-10-03T11:59:17.357 回答
4

考虑一个带有 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 紧密耦合。除了 FormA 类型的父级之外,FormB 不能有其他父级。

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

rp

于 2008-10-22T18:38:04.943 回答
4

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

假设我在课堂上的某个方法中有此代码:

this.some_object = new SomeObject();

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

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

那么第一个例子可以只使用注入的SomeObject。这在测试期间很有用。通过正常操作,您可以使用繁重的、使用数据库的、使用网络的类等,同时通过轻量级的模拟实现进行测试。使用紧密耦合的代码,您无法做到这一点。

您可以通过使用依赖注入容器来简化这项工作的某些部分。您可以在 Wikipedia 上阅读有关 DI 的更多信息:http ://en.wikipedia.org/wiki/Dependency_injection 。

有时很容易走得太远。在某些时候,您必须使事情具体化,否则您的程序的可读性和可理解性会降低。所以主要在组件边界使用这种技术,并且知道你在做什么。确保您正在利用松散耦合。如果没有,你可能在那个地方不需要它。DI 可能会使您的程序更复杂。确保你做出了很好的权衡。换句话说,保持良好的平衡。与设计系统时一样。祝你好运!

于 2008-10-23T06:44:30.890 回答
3

用简单的语言来说,松散耦合意味着它不依赖于其他事件的发生。它独立执行。

于 2014-03-20T05:34:13.250 回答
3

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

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

术语“系统”在这里可能会造成混淆,因此请仔细分析情况。

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

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

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

  • VaxClusters
  • Linux 集群

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

  • Semetrical-Multi-Processing (SMP) 操作系统 - 例如 Fedora 9
  • 多线程 CPU
  • 多核 CPU

在当今的计算中,两者都在单个整体系统中运行的例子并不少见。例如,以运行 Fedora 9 的现代奔腾双核或四核 CPU 为例——这些是紧密耦合的计算系统。然后,将其中的几个组合到一个松散耦合的 Linux 集群中,您现在可以同时进行松散耦合和紧耦合计算!哦,现代硬件是不是很棒!

于 2008-10-23T17:56:41.843 回答
2

这里有一些长答案。不过原理很简单。我从维基百科提交了开幕词:

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

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

于 2008-12-31T13:10:23.823 回答
2

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

  1. 如果对代码段 B 存在任何可能的修改,这将迫使代码段 A 进行更改以保持正确性,则代码段 A 与代码段 B 紧密耦合。

  2. 如果没有可能对代码段 B 进行任何修改而需要对代码段 A 进行更改,则代码段 A 与代码段 B 不紧密耦合。

这将帮助您验证代码片段之间的耦合程度。有关推理,请参阅此博客文章: http: //marekdec.wordpress.com/2012/11/14/loose-coupling-tight-coupling-decoupling-what-is-that-all-about/

于 2012-11-14T21:51:19.063 回答
2

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

---A.java---

package interface_package.loose_coupling;

public class A {

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

---B.java---

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 类定义了一个 Contract,因为 InterfaceClass 具有 B 类的抽象方法,可以被任何其他类(例如 A)访问。

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

在 MainClass 中,我们创建了 A 类和 B 类的对象。通过传递 B 类的对象,即 objb,调用 A 的显示方法。A 的显示方法将与 B 类的对象一起调用。

现在谈论松散耦合。假设将来您必须将B类的名称更改为ABC,那么您不必在B类的显示方法中更改其名称,只需创建新的对象(ABC类)并将其传递给MailClass中的显示方法。您无需更改 A 类中的任何内容

参考: http: //p3lang.com/2013/06/loose-coupling-example-using-interface/

于 2015-01-06T08:56:39.850 回答
1

您可以阅读更多关于“松散耦合”的通用概念。

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

于 2008-10-22T18:34:29.197 回答
-7

一般来说,松散耦合是两个参与者在相同的工作负载上相互独立工作。因此,如果您有 2 个 Web 服务器使用相同的后端数据库,那么您会说这些 Web 服务器是松散耦合的。紧密耦合可以通过在一台 Web 服务器上拥有 2 个处理器来举例说明……这些处理器是紧密耦合的。

希望这有点帮助。

于 2008-10-22T18:27:29.413 回答