我似乎无法理解"松耦合"的概念.我想这不利于单词"宽松"通常有负面的含义,所以我总是忘了松耦合是一个很好的事情.
有人请展示一些说明这个概念的"之前"和"之后"代码(或伪代码)吗?
考虑一个简单的购物车应用程序,该应用程序使用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);
}
}
特定于购物车订单项或购物车集合或订单的实施的逻辑仅限于该类.因此,我们可以更改任何这些类的实现,而无需更改其他类.我们可以通过改进设计,引入接口等来进一步解耦,但我认为你明白了这一点.
许多集成产品(特别是苹果公司),如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耦合.
抱歉,"松散耦合"不是编码问题,这是一个设计问题.术语"松散耦合"与"高内聚力"的理想状态密切相关,相反但是相互补充.
松散耦合仅仅意味着应该构造单独的设计元素,以便减少他们需要了解的关于其他设计元素的不必要信息的数量.
高凝聚力有点像"紧密耦合",但高凝聚力是一种状态,其中真正需要彼此了解的设计元素被设计为使它们干净和优雅地协同工作.
关键是,一些设计元素应该知道其他设计元素的细节,因此它们应该以这种方式设计,而不是偶然的.其他设计元素不应该知道其他设计元素的细节,因此它们应该有目的地设计,而不是随机设计.
实现这一点留给读者:).
紧密耦合的代码依赖于具体的实现.如果我需要我的代码中的字符串列表,我声明它(在Java中)
ArrayListmyList = new ArrayList ();
然后我依赖于ArrayList实现.
如果我想将其更改为松散耦合的代码,我将引用设置为接口(或其他抽象)类型.
ListmyList = new ArrayList ();
这让我无法调用任何方法对myList
那具体到ArrayList实现.我只限于List接口中定义的那些方法.如果我稍后决定我真的需要一个LinkedList,我只需要在一个地方更改我的代码,我创建新的List,而不是在我调用ArrayList方法的100个地方.
当然,您可以使用第一个声明来实例化ArrayList,并限制自己不使用任何不属于List接口的方法,但使用第二个声明会使编译器保持诚实.
这里的答案之间的差异程度说明了为什么要把握这个难以理解的概念,而是将其简单地描述为:
为了让我知道,如果我向你扔球,那么你可以抓住它我真的不需要知道你多大了.我不需要知道你早餐吃了什么,我真的不在乎你的第一次挤压是谁.我需要知道的是,你可以抓住.如果我知道这一点,那么我不在乎你是不是我向你或你的兄弟扔球.
使用非动态语言(如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的服务可以使用其中任何一种,而不是真的给它一个该死的.因此,紧密耦合的服务可能会直接影响被捕获的ie
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框架等,还有其他一些方法.
您可以将(紧密或松散)耦合视为字面上将特定类与依赖另一类所分离的努力量.例如,如果您的类中的每个方法都在底部有一个最终阻塞,您调用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; } }
此代码声明此类具有CustomizableGridService的依赖性.它不是直接引用服务的确切实现,而是简单地声明它需要该服务的一些实现.然后在运行时,系统解析该依赖关系.
如果不清楚,您可以在此处阅读更详细的说明:
http://en.wikipedia.org/wiki/Dependency_injection
想象一下,ABCCustomizableGridService是我打算在这里挂钩的imlpementation.
如果我选择,我可以将其取出并用XYZCustomizableGridService或StubCustomizableGridService替换它,而不会对具有此依赖关系的类进行任何更改.
如果我直接引用了ABCCustomizableGridService,那么我需要对那个/那些reference/s进行更改,以便交换另一个服务实现.
耦合与系统之间的依赖关系有关,系统可以是代码模块(函数,文件或类),管道中的工具,服务器 - 客户端进程等等.依赖性越不普遍,它们就越"紧密耦合",因为改变一个系统需要改变依赖它的其他系统.理想的情况是"松散耦合",其中一个系统可以更改,依赖它的系统将继续工作而无需修改.
实现松散耦合的一般方法是通过定义良好的接口.如果两个系统之间的交互很好地定义并且在两侧都遵守,那么在确保不破坏约定的同时修改一个系统变得更容易.它通常在实践中发生,没有建立明确定义的界面,导致设计松散和紧密耦合.
一些例子:
应用程序取决于库.在紧密耦合下,app会破坏较新版本的lib.谷歌为"DLL地狱".
客户端应用程序从服务器读取数据.在紧密耦合下,对服务器的更改需要在客户端进行修复.
两个类在面向对象的层次结构中交互.在紧耦合下,对一个类的更改需要更新另一个类以匹配.
多个命令行工具在管道中进行通信.如果它们紧密耦合,对一个命令行工具的版本的更改将导致读取其输出的工具中的错误.
耦合是指不同的类之间紧密地连接在一起。紧密耦合的类包含大量的交互和依赖关系。
松散耦合的类是相反的,因为它们彼此之间的依赖关系被最小化,而是相互依赖定义良好的公共接口。
乐高玩具,即SNAP在一起的玩具,可以认为是松散耦合的,因为您可以将零件拼合在一起并构建所需的任何系统。但是,拼图游戏的零件紧密相连。您不能从一个拼图游戏(系统)中提取一块并将其捕捉到另一个拼图中,因为系统(拼图)非常依赖于针对该特定“设计”而构建的非常具体的部分。乐高积木的建造方式更为通用,因此可以在您的Lego House或我的Lego Alien Man中使用。
参考:https : //megocode3.wordpress.com/2008/02/14/coupling-and-cohesion/