目录

Understanding Dependencies

What is a Dependency?

Why are Dependencies Bad?

Dependency Types

Additional Dependency Characteristics

Interface Implementation Dependencies

Compile-time and Runtime Dependencies

Visible and Hidden Dependencies

Direct and Indirect Dependencies

Unnecessarily Extensive Dependencies

Local and Context Dependencies

Standard vs. Custom Class/Interface Dependencies

Summary


What is a Dependency?

当一个类A使用另一个类或接口B时,那么A依赖于B. A 如果没有B就不能工作,也不能被重新利用,在这种情况下,A叫做 "dependant"(受抚养者,依赖他人的人) and B叫做 "dependency"( 被依赖者,从属物). A dependant 依赖于它的dependencies.

像个相互使用的类叫做 "coupled"(耦合). 类之间的耦合可以是松散的,也可以是紧密的,或者介于两者之间.耦合的紧密性不是二元的.它既不“松”也不“紧”. 紧密度是连续的,而不是离散的. 您还可以将依赖关系描述为“强”或“弱”. 紧密耦合导致了强依赖性, 松散耦合导致弱依赖,甚至在某些情况下没有依赖关系.

Dependencies, or couplings, 依赖或者耦合性是单向的. A依赖B,并不意味着B也依赖A

Why are Dependencies Bad?

Dependencies不好是因为它们减少了重用reuse. Decreased reuse有很多不好的地方. 重用通常对开发速度、代码质量、代码可读性等都有积极的影响。

一个例子可以很好地说明依赖关系是如何影响重用的:

你有一个 CalendarReader类从 XML 文件里面读取calendar event 事件列表. calendar arreader的实现概述如下:

public class CalendarReader {public List readCalendarEvents(File calendarEventFile){//open InputStream from File and read calendar events.}
}

readCalendarEvents方法有一个 File 对象作为参数. 因此,这个方法依赖于File类. 对File类的这种依赖意味着CalendarReader只能从文件系统中的本地文件中读取日历事件. 它无法从网络连接、数据库或类路径上的资源中读取事件文件. 您可以说,CalendarReader与File类紧密耦合,从而与本地文件系统紧密耦合。

下面是降低耦合的一种方法,把 File参数,改成 InputStream参数:

public class CalendarReader {public List readCalendarEvents(InputStream calendarEventFile){//read calendar events from InputStream}
}

一个输入流 InputStream 可以从 File 对象, a network Socket(网络套接字), a URLConnection 类, a Class object (Class.getResourceAsStream(String name)), (数据库)a column in a database via JDBC etc. 现在,CalendarReader不再耦合到本地文件系统。它可以从许多不同的来源读取事件文件。

参数为InputStream 的readCalendarEvents() 方法变得重用性更好与之前比较,与本地文件的耦合性解除了,变成和 InputStream 类耦合.  InputStream 比 File 更灵活,但是也不意味着 CalendarReader 是可以 100% 重用的. 例如,它仍然不能轻松地从NIO通道读取数据。

Dependency Types

依赖的类型有:

  • Class Dependencies:类依赖
  • Interface Dependencies:接口依赖
  • Method / Field Dependencies:方法/字段依赖

Class dependencies依赖于类.例如,下面代码框中的方法接受一个字符串作为参数。因此它依赖于String类。

public byte[] readFileContents(String fileName){//open the file and return the contents as a byte array.
}

Interface dependencies 依赖于接口. 例如,下面代码框中的方法接受一个CharSequence作为参数。CharSequence是一个标准的Java接口(in the java.lang package). CharBuffer、String、StringBuffer和StringBuilder都实现了CharSequence接口,所以这些类的任何实例都可以用作该方法的参数。

public byte[] readFileContents(CharSequence fileName){//open the file and return the contents as a byte array.
}

Method or field dependencies 依赖于对象的具体方法或字段. 不管对象的类是什么,或者它实现了什么接口,只要它有一个所需类型的方法或字段. 下面的方法演示了一个方法依赖项 method dependency. 该方法依赖于一个作为参数给出的对象的类(fileNameContainer)中"getFileName" 方法.注意,依赖项在方法声明中是不可见的:

public byte[] readFileContents(Object fileNameContainer){Method method   = fileNameContainer.getClass().getMethod("getFileName", null);String fileName = method.invoke(fileNameContainer, null);//open the file and return the contents as a byte array.
}

Method or field dependencies 通常使用反射或者目标方法或者字段.比如, Butterfly Persistence 使用反射检测类的getters 和 setters . 没有 getters and setters, Butterfly Persistence就不能读写数据库中的类,这样 Butterfly Persistence 依赖于 getters and setters. Hibernate (a similar ORM API) 可以直接使用 getters and setters, 或者访问字段fields ,也是通过反射.这样Hibernate要么有方法依赖项,要么有字段依赖项。

Method (or "function") dependencies 也可以在支持函数指针或方法指针作为参数传递给其他方法的语言中看到。例如,c#委托。

Additional Dependency Characteristics

Dependencies can be compile-time, runtime, visible, hidden, direct, indirect, contextual etc.依赖项可以是编译时、运行时、可见的、隐藏的、直接的、间接的、上下文的等等

Interface Implementation Dependencies:接口实现的依赖关系

如果 class A 依赖于接口 I, 那么A不依赖于I的具体实现. 但是,A依赖于I的某个实现. 如果A不实现I中的方法,就不能工作. 因此,每当一个类依赖于一个接口时,这个类也依赖于一个实现。

接口的方法越多,开发人员为接口提供自己的实现的机会就越少,除非需要这样做.因此,一个接口拥有的方法越多,开发人员就越有可能坚持使用该接口的默认实现. 换句话说,一个接口变得越大、越复杂,它与默认实现的耦合就越紧密!

因为interface implementation dependencies, 您不应该盲目地向接口添加功能.如果功能可以封装在它自己的脚本中,在它自己的接口之后,尽量这样做:

下面就是一个例子。代码示例显示了一个分层树结构的树节点,The code example shows a tree node for a hierarchical tree structure.

public interface ITreeNode {public void            addChild(ITreeNode node);public List<ITreeNode> getChildren();public ITreeNode       getParent();
}

假设您希望能够计算给定节点的后代. 首先,您可能想要向ITreeNode接口添加countents()方法. 但是,如果您这样做,任何想要实现ITreeNode接口的人也必须实现countent()方法。

相反,您可以实现一个派生计数器类,该类可以遍历一个ITreeNode实例,计算该实例的所有派生。这个派生计数器可以在ITreeNode接口的不同实现中重用. 您刚刚为用户省去了实现countents()方法的麻烦,即使他们需要实现ITreeNode接口!

Compile-time and Runtime Dependencies

可以在编译时解析的依赖项是编译时依赖项. 直到运行时才能解析的依赖项是运行时依赖项。与运行时依赖项相比,开发人员更容易看到编译时依赖项, 但有时运行时依赖关系可能更灵活.比如, Butterfly Persistence 在运行时检测类的getter和setter,并自动将它们映射到该类的数据库表. 这是将类映射到数据库表的一种非常简单的方法.

Visible and Hidden Dependencies

visible dependency 是开发人员可以从类接口看到的依赖项. 如果不能从类的接口看到依赖项,则它是一个隐藏的依赖项。 hidden dependency.

在之前的例子中,String 和 CharSequence是 readFileContents()方法的依赖项,是可见的依赖项.它们在方法声明中是可见的,方法声明是类接口的一部分. method dependencies 方法依赖类型的 readFileContents() 方法需要一个对象作为参数,是不可见invisible.您无法从接口查看readFileContents()方法是否调用了fileNameContainer.toString()来获取文件名,或者它实际上调用了getFileName()方法。

另一个隐藏依赖项的例子是对静态单例的依赖,或者来自方法内部的静态方法. 您无法从接口看到一个类是否依赖于静态方法或静态单例对象。

可以想象,隐藏的依赖关系是不好的。 对于使用具有隐藏依赖项的类的开发人员来说,很难检测到它们。他们只能通过检查代码来查看它们。

这并不等于说永远不要使用隐藏的依赖项.隐藏的依赖关系通常是提供合理的默认值 providing sensible defaults.例如,在这个例子中,它可能不是一个问题:

public class MyComponent{protected MyDependency dependency = null;public MyComponent(){this.dependency = new MyDefaultImpl();}public MyComponent(MyDependency dependency){this.dependency = dependency;}
}

MyComponent 有一个隐藏的依赖 MyDefaultImpl,. 但如果MyDefaultImpl没有任何危险的副作用, 那么这个隐藏的依赖就不是危险的

Direct and Indirect Dependencies

依赖项可以是直接依赖项,也可以是间接依赖项.如果a类使用B类,那么a直接依赖于B. 如果A依赖于B, B依赖于C,那么A间接依赖于C. 如果你不能在没有B的情况下使用A,也不能在没有C的情况下使用B,那么你也不能在没有C的情况下使用A。

间接依赖关系也称为链式依赖关系, or "transitive dependencies" (in "Better, Faster, Lighter Java" by Bruce A. Tate and Justin Gehtland)

Unnecessarily Extensive Dependencies:不必要的扩展依赖关系

有时组件依赖的信息比它们执行工作所需的信息还要多.例如,设想一个web应用程序的登录组件. 登录组件只需要一个用户名和一个密码,并将返回与这些匹配的user对象(如果有的话)。界面可以是这样的:

public class LoginManager{public User login(HttpServletRequest request){String user     = request.getParameter("user");String password = request.getParameter("password");//read user and return it.}
}

Calling the component would look like this:

LoginManager loginManager = new LoginManager();
User         user         = loginManager.login(request);

看起来很简单,对吧?即使登录方法需要更多的参数,也不需要更改调用代码。

但是登陆方法对 HttpServletRequest 接口有我称之为 "unnecessarily extensive dependency. . LoginManager只需要一个用户名和一个密码来查找一个用户,但是使用HttpServletRequest作为登录方法的参数.  HttpServletRequest 包含许多 LoginManager 不需要的内容

依赖于 HttpServletRequest 接口造成两种问题:

  1. The LoginManager 不能在没有 HttpServletRequest 实例的情况下重用. 这造成测试 LoginManager 很困难. 你需要模拟一个HttpServletRequest 实例
  2. LoginManager requires 需要用户名和密码作为参数. 这也是一种不必要的依赖.

A much better interface for the LoginManager's login method would be:

public User login(String user, String password){//read user and return it.
}

But look what happens to the calling code now:

LoginManager loginManager = new LoginManager();
User user = loginManager.login(request.getParameter("user"),request.getParameter("password"));

它变得更加复杂。对于一个需要5个请求参数来完成其工作的组件,情况会更糟. 这是开发人员创建不必要的大量依赖项的主要原因。以简化调用代码。

Local and Context Dependencies

在开发应用程序时,通常将应用程序拆分成小的组件. 其中一些组件是通用组件,在其他应用程序中也很有用. 其他组件是特定于应用程序的,在应用程序之外没有任何用途。

对于通用组件,属于组件(或API)的任何类都是“本地的”. 应用程序的其余部分是“上下文”。 "context". 应用程序的其余部分是“上下文”叫做 "context dependency". Context dependencies 是不好的,因为它使通用组件在应用程序之外也不可用。

. Context dependencies经常会出现在开发人员试图简化其应用程序的设计时,这方面的一个很好的例子是请求处理应用程序,比如消息队列连接的应用程序或web应用程序。

假设有一个应用程序以XML形式接收请求、处理请求并以XML形式发回结果. 在处理过程中,XML请求由几个单独的组件处理. 每个组件都需要不同的信息,其中一些信息是由早期组件生成的。每个组件都需要不同的信息,其中一些信息是由早期组件生成的。.然后,处理组件可以从该请求对象读取信息,并附加更多信息供以后的处理组件使用. 通过将此请求对象作为参数,每个请求处理组件都依赖于此请求对象。请求对象是特定于应用程序的,因此导致每个请求处理组件具有上下文依赖关系。

Standard vs. Custom Class/Interface Dependencies

在许多情况下,组件最好依赖于标准Java(或c#等)包中的类或接口.任何人都可以使用这些类和接口,从而更容易地满足这些组件依赖关系。此外,这些类不太可能更改并导致应用程序编译失败。

但在某些情况下,依赖于JDK类并不是最好的做法.例如,假设一个方法需要4个字符串来进行配置. 然后你的方法接受4个字符串作为参数。例如,驱动程序名、数据库url、用户名和数据库连接所需的密码. 如果所有这4个字符串总是一起使用,那么对于该方法的用户来说,如果将这4个字符串分组到一个类中,可能会更清楚, 并传递类的实例,而不是4个字符串。

Summary

. 一般来说,接口依赖比类依赖更可取. 方法和字段依赖关系可能非常有用,但请记住,它们通常也是隐藏的依赖关系,而隐藏的依赖关系使组件的用户更难检测到它

Interface implementation dependencies比你想象的更普遍.通过让接口尽量简单来限制他们

Hidden dependencies 可能是危险的,但是由于运行时依赖项有时也是隐藏的依赖项,所以您不一定总能做出选择。

请记住,即使一个组件不直接依赖于另一个组件,它仍然可能间接依赖于另一个组件。虽然间接依赖关系的限制通常较少,但它们仍然是依赖关系。

尽量避免不必要的广泛依赖。但是请记住,当您将多个参数分组到一个类中时,经常会出现不必要的大量依赖项。这是一种常见的重构,通常是为了使代码看起来更简单,但是正如您现在看到的,这也会导致不必要的大量依赖。

c# 耦合性(依赖性)相关推荐

  1. 干货!C++程序的耦合性设计

    1.什么是耦合? 耦合其实就是程序之间的相关性. 程序之间绝对没有相关性是不可能的,否则也不可能在一个程序中启动,如下图: 这是一个Linux中socket TCP编程的程序流程图,在图中的TCP服务 ...

  2. 软件架构设计之系统耦合性拆分

    软件架构设计之系统模块的拆分 基本概念 功能模块 循环依赖问题 模块拆分原则 高内聚性 低耦合性 模块拆分方式 模块拆分示例 业务需求 业务分析 项目原始代码 需求重构 项目代码重构 总结 基本概念 ...

  3. 浅谈Redis与MySQL的耦合性以及利用管道完成MySQL到Redis的高效迁移

    ㈠ Redis 与 MySQL 的耦合性 在业务架构早期.我们便该"吃着碗里的看着锅里的".切莫让MySQL 有梦.而Redis 无心 毕竟.有些关系型的结构不适合放到Redis跑 ...

  4. C++设计模式-继承与多态影响耦合性(最基础的简单工厂模式小实例)

    继承与多态影响耦合性(最基础的简单工厂模式小实例) 原理: 通过继承和虚函数的方式修改某个子类对应函数的功能: 通过简单工厂模式到底实例化谁; 如果要增加复杂的运算只有增加响应的子类,以及工厂的分支即 ...

  5. 浅谈 Redis 与 MySQL 的耦合性以及利用管道完成 MySQL 到 Redis 的高效迁移

    http://blog.csdn.net/dba_waterbin/article/details/8996872 ㈠ Redis 与 MySQL 的耦合性               在业务架构早期 ...

  6. 原创经典-为什么Spring中的IOC(控制反转)能够降低耦合性(解耦)?

    学到Spring老师一定会讲一句话:IOC能够降低耦合性.可是为什么会降低耦合性,许多老师就讲不清了或者干脆不讲,下面我们通过举一些栗子,一步一步地说明为什么IOC能够降低耦合性. 前言 通过本文你将 ...

  7. 模块独立(耦合性与内聚性)

    模块独立性指模块不依赖其他模块独立完成功能的程度.模块独立程度的两个定性标准度量是耦合和内聚. 耦合性衡量不同模块彼此之间相互依赖(连接)的紧密程度,耦合要低,即每个模块与其他模块的关系要简单:内聚性 ...

  8. 耦合性(耦合度) -- Coupling

    耦合性是程序结构中各个模块之间相互关联的度量.它取决于各个模块之间接口的复杂程度.调用模块的方式以及哪些信息通过接口. 一般模块之间可能的连接方式有七种,构成耦合性的七种类型.它们之间的关系为(由弱到 ...

  9. 耦合性(或称“耦合度”)

    耦合性(或称"耦合度") 英文 : coupling 耦合性是程序结构中各个模块之间相互关联的度量.它取决于各个模块之间接口的复杂程度.调用模块的方式以及哪些信息通过接口. 一般模 ...

最新文章

  1. Pytorch——YOLOv3
  2. Java8 的 Stream 流式操作之王者归来
  3. VTK:Utilities之ArrayRange
  4. 程序包com.sun.istack.internal不存在
  5. C语言程序所以,C语言程序(1)
  6. BZOJ4152 AMPPZ2014 The Captain 【最短路】【贪心】*
  7. 射频通信接收机设计的主要结构
  8. RS485芯片UN485E的特点及其应用
  9. 【加拿大签证】加拿大签证办理GCKey注册说明【加拿大签证网上办理注册】
  10. 随笔-学习编程有没有必要做笔记?如何做笔记?
  11. 45度回合RPG网页游戏《烽烟OL》v1.3正式开源!
  12. Hadoop3.2.0 HDFS HA ( Quorum Journal Manager )
  13. 好用简单、且永久免费的内网穿透工具
  14. graphpad如何检测方差齐_【求助】急求如何用Graphpad Prism6 做析因设计方差分析?谢谢各位大神...
  15. HyperLPR车牌识别技术算法之车牌精定位
  16. java 规范 阿里巴巴_阿里巴巴编码规范java
  17. Dubbo线程池问题思考Thread pool is EXHAUSTED!
  18. eclipse SVN A conflict in the working copy obstructs the current operation
  19. (转)关于用户管理模块
  20. HackingLab的一套渗透测试题

热门文章

  1. 新手在Visual Studio Code使用go语言打印hello wrod时可能遇到的问题的解决方案 。
  2. 《京韵大鼓——孟姜女》(骆玉笙)(唱词文本)
  3. Django中ORM中queryset方法详解
  4. 【信息安全】-经典面试题吐血整理
  5. 在Java中,以下数据类型中,需要内存最多的是()
  6. Spring中Scope源码分析
  7. 褚君浩院士:传感器,让我们的敏感神经更敏感
  8. SwiftUI 小技巧之如何使用十六进制颜色color
  9. 路径规划: a star, A星算法详解
  10. 由浅入深,谈谈文件上传的优化思路