什么是"loose coupling?" 请举例说明



我似乎无法理解"松耦合"的概念。我想"松散"这个词通常具有负面含义是无济于事的,所以我总是忘记松耦合是一件好事。

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

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

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

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

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

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

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

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

高内聚有点

像"紧密耦合",但高内聚是一种状态,其中设计出真正需要相互了解的设计元素,以便它们干净而优雅地协同工作。

关键是,一些设计元素应该

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

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

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

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

对于 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方法,因此需要 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的调用可能会执行以下操作:

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

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

这意味着Catch服务没有与CatcherACatcherB紧密耦合。

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

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

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

然后我依赖于 ArrayList 实现。

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

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

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

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

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

定义

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

高耦合

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

松耦合

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

这是一个非常笼统的概念,因此代码示例不会给出全貌。

一个在工作的人对我说,"模式就像分形,当你放大到非常近的时候,当你放大到架构级别时,你可以看到它们。

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

http://en.wikipedia.org/wiki/Loose_coupling

就具体代码示例而言...

这是我最近使用的一个松散耦合,来自Microsoft.Practices.CompositeUI的东西。

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

此代码声明此类依赖于可自定义网格服务。 它不只是直接引用服务的确切实现,而是简单地声明它需要该服务的一些实现。 然后在运行时,系统解析该依赖项。

如果不清楚,您可以在此处阅读更详细的说明:

http://en.wikipedia.org/wiki/Dependency_injection

想象一下,ABCCustomizableGridService是我打算在这里连接的。

如果我愿意,我可以将其拉出并替换为XYZCustomizableGridService或StubCustomizableGridService,而与此依赖项的类没有任何变化。

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

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

实现松散耦合的一般方法是通过定义良好的接口。 如果两个系统之间的相互作用得到很好的定义,并在双方都遵守,那么在确保公约不被破坏的同时修改一个系统就会变得更容易。 在实践中通常会出现没有建立明确定义的接口的情况,从而导致设计草率和紧密耦合。

一些例子:

  • 应用程序依赖于库。 在紧密耦合下,应用程序在较新版本的库上中断。 谷歌为"DLL Hell"。

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

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

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

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

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

乐高积木,SNAP组合在一起的玩具将被认为是松散耦合的,因为您可以将碎片卡在一起并构建您想要的任何系统。然而,拼图游戏有紧密耦合的碎片。你不能从一个拼图(系统(中取出一块拼图,然后把它卡入另一个拼图,因为系统(拼图(非常依赖于特定于该特定"设计"构建的非常特定的块。乐高积木以更通用的方式建造,因此它们可以在您的乐高之家或我的乐高外星人中使用。

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

考虑一个带有 FormA 和 FormB 的 Windows 应用程序。窗体 A 是主窗体,它显示窗体 B。想象一下,FormB 需要将数据传回其父级。

如果您这样做:

class FormA 
{
    FormB fb = new FormB( this );
    ...
    fb.Show();
}
class FormB 
{
    FormA parent;
    public FormB( FormA parent )
    {
        this.parent = parent;
    }     
}

FormB 与 FormA 紧密耦合。窗体 B 不能有除窗体 A 类型的父项之外的其他父项。

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

RP

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

假设我在类的某个方法中有以下代码:

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 可能会使程序更加复杂。确保你做出良好的权衡。换句话说,保持良好的平衡。与设计系统时一样。祝你好运!

在计算机科学中,"松散耦合"还有另一个含义,没有其他人在这里发布过,所以......来了 - 希望你能给我一些选票,这样就不会在堆的底部丢失!当然,我回答的主题属于对该问题的任何综合回答......即:

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

术语"系统"在这里可能会令人困惑,因此请仔细分析情况。

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

术语">

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

首先,由于这是问题的内容,松散耦合系统的一些示例:

  • Vax集群
  • Linux 集群

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

  • Semetrical-Multi-Processing (SMP( 作業系統 - 例如 Fedora 9
  • 多线程处理器
  • 多核处理器

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

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

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

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

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

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

    如果对 B 段存在
  1. 任何可能的修改,则会强制更改 A 段以保持正确性,则代码的 A 段与代码段 B 紧密耦合。

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

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

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

---答.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 为 B 类

定义了一个协定,因为 InterfaceClass 具有 B 类的抽象方法,可以由任何其他类(例如 A(访问。

在 A 类中,

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

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

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

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

您可以阅读有关"松耦合"的一般概念的更多信息。

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

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

希望这有所帮助。

最新更新