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

时间:2008-10-22 18:23:09

标签: oop language-agnostic loose-coupling

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

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

20 个答案:

答案 0 :(得分:163)

考虑一个简单的购物车应用程序,该应用程序使用CartContents类来跟踪购物车中的项目以及用于处理购买的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类)如何依赖于CartContents和CartEntry类的实现细节。如果我们试图改变这种逻辑以允许折扣,我们可能不得不改变所有3个类。此外,如果我们更改为使用List集合来跟踪我们必须更改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);
    }
}

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

答案 1 :(得分:150)

许多集成产品(尤其是Apple),如 iPods iPad 都是紧密耦合的一个很好的例子:一旦电池耗尽,您可能会购买新设备因为电池是焊接固定的并且不会松动,因此更换非常昂贵。松散耦合的播放器可以毫不费力地更换电池。

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

答案 2 :(得分:51)

我将以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。或者换句话说,它们与父类“分离”或“松散耦合”。

但是,如果我们需要两者之间的沟通呢?那么,我们可以使用像Event Model这样的进一步抽象来确保父代码永远不需要与您创建的API结合。

答案 3 :(得分:36)

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

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

高凝聚力有点像“紧耦合”,但高凝聚力是一种状态,其中真正需要彼此了解的设计元素被设计为使它们干净和优雅地协同工作。

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

实现这个是留给读者的练习:)。

答案 4 :(得分:33)

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

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

然后我依赖于ArrayList实现。

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

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

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

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

答案 5 :(得分:24)

这里的答案之间的差异程度说明了为什么要把握这个难以理解的概念,而是将其简单地描述为:

为了让我知道,如果我向你扔球,那么你可以抓住它我真的不需要知道你多大了。我不需要知道你早餐吃了什么,我真的不在乎你的第一次挤压是谁。我需要知道的是,你可以抓住。如果我知道这一点,那么我不在乎你是不是我向你或你的兄弟扔球。

使用c#或Java等非动态语言,我们通过Interfaces实现这一目标。所以我们假设我们有以下界面:

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和CatcherB都实现了Catch方法,因此需要Catcher的服务可以使用其中任何一种,而不是真的给它一个该死的。因此,一个紧密耦合的服务可能会直接导致陷入困境,即

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的calss可能会执行以下操作:

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

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

这意味着Catch服务与CatcherA或CatcherB没有紧密耦合。

对于像这样的松散耦合服务,例如使用IoC框架等,还有其他一些方法。

答案 6 :(得分:23)

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

答案 7 :(得分:12)

定义

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

高耦合

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

松散耦合

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

答案 8 :(得分: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是我打算在这里联系起来的imlpementation。

如果我选择,我可以将其取出并将其替换为XYZCustomizableGridService或StubCustomizableGridService,而不会对具有此依赖关系的类进行任何更改。

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

答案 9 :(得分:6)

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

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

一些例子:

  • 应用程序取决于库。在紧密耦合下,app会破坏较新版本的lib。谷歌的“DLL地狱”。

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

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

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

答案 10 :(得分:5)

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

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

乐高积木,SNAP合在一起的玩具将被认为是松散耦合的,因为你可以将这些碎片拼凑在一起并构建你想要的任何系统。然而,拼图游戏中的拼图很紧密。你不能从一个拼图游戏(系统)中取出一块并将其拼接到另一个拼图中,因为系统(拼图)非常依赖于特定于特定“设计”构建的非常具体的部分。 legos以更通用的方式构建,以便它们可以在您的Lego House或我的Lego Alien Man中使用。

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

答案 11 :(得分: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。 这在测试期间很有用。通过正常操作,您可以使用繁重的,使用数据库的,使用网络的类等,同时通过轻量级模拟实现的测试。 使用紧密耦合的代码,您无法做到这一点。

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

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

答案 12 :(得分:3)

考虑使用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

答案 13 :(得分:3)

在计算机科学中,“松散耦合”还有另一个含义,没有其他人在这里张贴过,所以......这里 - 希望你能给我一些票,所以这不会丢失在底部堆!我的回答的主题属于问题的任何综合答案......即:

术语“松散耦合”首先进入计算,作为在多CPU配置中用作CPU架构的形容词。它的对应术语是“紧耦合”。松散耦合是指CPU不共享许多资源,而紧密耦合就是它们的共同点。

术语“系统”在这里可能令人困惑,所以请仔细解析情况。

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

多线程和多核CPU发明之前引入了紧密耦合和松散耦合这两个术语,因此这些术语可能需要一些同伴来完全阐明当前的情况。事实上,今天很可能有一个系统在一个整体系统中包含两种类型。关于当前的软件系统,有两种常见的体系结构,每种体系结构中的一种,这些体系结构很常见,这些体系结构应该是熟悉的。

首先,因为问题是关于松散耦合系统的一些例子:

  • VaxClusters
  • Linux群集

相反,一些紧密耦合的例子:

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

在今天的计算中,在单个整体系统中运行的示例并不罕见。例如,采用运行Fedora 9的现代奔腾双核或四核CPU - 这些是紧密耦合的计算系统。然后,将它们中的几个组合在一个松散耦合的Linux集群中,您现在可以进行松散且紧密耦合的计算!哦,不是现代硬件精彩!

答案 14 :(得分:3)

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

答案 15 :(得分:2)

这里有一些很长的答案。但原则很简单。我从wikipedia提交了开场陈述:

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

交易的每一端都明确要求,并对另一端做出一些假设。“

答案 16 :(得分:2)

答案 17 :(得分: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");
}
}

--- --- InterfaceClass

package interface_package.loose_coupling;

public interface InterfaceClass {

void display();
String getVar();
}

--- --- MainClass

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类的显示方法中更改其名称,只需创建new(ABC类)的对象并将其传递给MailClass中的display方法即可。您不必更改A类中的任何内容

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

答案 18 :(得分:0)

您可以详细了解"loose coupling"的一般概念。

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

答案 19 :(得分:-8)

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

希望这有点帮助。