去香蕉松散耦合和依赖注入
随着我们依赖注入框架(Spring注释)的最新增加,创建DI管理组件的边际成本似乎已经达到了一些关键的新门槛。 虽然以前有一个与spring相关的开销(XML和额外的indirections吨),依赖注入似乎已经开始去了很多模式去的地方; 他们走在引擎盖下并“消失”。
这样做的结果是与大量组件相关的概念开销变得可以接受。 我们可以说,我们可以建立一个系统,在这个系统中,大多数类只暴露一种公共方法,并通过将这些部分像疯子一样聚合来构建整个系统。 在我们的例子中,给出了一些事情; 您的应用程序的用户界面具有塑造最高级服务的一些功能要求。 后端系统控制下部。 但在这两者之间,一切都需要争取。
我们经常讨论的是,为什么我们将课堂分类,原则应该是什么? 有几件事是确定的; 立面图案已经死亡并被埋葬。 包含多个不相关功能的任何服务也倾向于分裂。 “无关特征”的解释比我之前做过的要严格得多。
在我们的团队中,这里有两种流行的思路:执行依赖限制分组; 单个类中的任何功能应该最好是所有注入依赖项的客户端。 我们是DDD项目,另一部分认为域限制分组(CustomerService或更细粒度的CustomerProductService,CustomerOrderService) - 注入依赖项的规范化使用不重要。
因此,在松散耦合的DI宇宙中,为什么我们将类逻辑分组?
编辑:duffymo指出,这可能正在走向一种功能性的编程风格; 这带来了国家的问题。 我们有相当多的“状态”对象代表(小)相关应用程序状态。 我们将这些注入到对此状态有合法需求的任何服务中。 (我们使用“状态”对象而不是常规域对象的原因是,Spring在未指定的时间构造这些对象,我认为这是一种轻微的解决方法或备用解决方案,让spring能够管理域对象的实际创建。这里)。
因此,例如任何需要OrderSystemAccessControlState的服务都可以注入,并且这些数据的范围并不为消费者所知。 一些与安全有关的状态通常用于许多不同的级别,但在中间级别上完全不可见。 我真的认为这违反了功能原理。 我甚至很难从OO的角度对这个概念进行调整 - 但只要注入状态是精确和强类型的,那么需求就是合法的,即用例是合理的。
优秀的面向对象设计的首要原则并不止于松散的耦合,而且还有高度的内聚性 ,在大多数讨论中被忽略。
高凝聚力
在计算机编程中,凝聚力是衡量一个单一模块的责任强度或集中程度的指标。 如果应用于面向对象的编程,如果服务于给定类的方法在很多方面倾向于相似,那么该类就被认为具有很高的内聚性。 在高度一致的系统中,代码的可读性和重用的可能性增加了,而复杂性仍然可以管理。
如果:
* The functionality embedded in a class, accessed through its methods,
have little in common.
* Methods carry out many varied activities, often using coarsely-grained or
unrelated sets of data.
低凝聚力(或“弱凝聚力”)的缺点是:
* Increased difficulty in understanding modules.
* Increased difficulty in maintaining a system, because logical changes in
the domain affect multiple modules, and because changes in one module
require changes in related modules.
* Increased difficulty in reusing a module because most applications
won’t need the random set of operations provided by a module.
当人们疯狂使用IoC容器时,有一件事会迷失方向,那就是内聚失去了,什么东西和事情的可追踪性变成了噩梦,因为所有的关系都被一堆XML配置所掩盖文件(我正在看你的Spring)和命名不佳的实现类。
你是在强调“分组”还是“分类”?
如果你问我们为什么要把事情分组,那么我会先看Medelt的“可维护性”,尽管我把它改为:“为了减少涟漪效应的潜在成本。”
暂时不要考虑组件(类,文件,不管它们可能是什么)之间的实际耦合,而是潜在的耦合,也就是这些组件之间的最大可能的源代码依赖关系数量。
有一个定理表明,给定一组链a-b-c-d-e,使得a取决于b等,改变e将改变c分量的概率不能大于可能性改变e然后会改变d。 而在真实的软件系统中,改变e会影响c的概率通常小于改变e会影响d的概率。
当然,你可能会说,这很明显。 但我们可以让它更加明显。 在这个例子中,d与e有直接的依赖关系,并且c对e有一个间接的(通过d)依赖关系。 因此,我们可以说统计上,主要由直接依赖形成的系统将比主要由间接依赖形成的系统受到更大的连锁反应。
鉴于此,在现实世界中,每个连锁反应都会花钱,我们可以说,主要由直接依赖形成的系统更新的连锁反应的成本将高于主要由系统形成的系统更新的连锁反应的成本间接依赖。
现在回到潜在的耦合。 可以证明,在绝对封装上下文(如Java或C#中,递归封装没有被广泛使用)中,所有组件都可能通过直接依赖关系或间接依赖关系与单个中间组件相互连接。 从统计上看,一个最小化组件间直接电位耦合的系统可以最大限度地减少任何更新引起的涟漪效应的潜在成本。
我们如何实现直接和间接潜在耦合之间的区别(就像我们还没有让猫走出包)? 封装。
封装是包含在建模实体中的信息只能通过该建模实体支持的接口进行交互才能访问的属性。 无法通过这些接口访问的信息(可以是数据或行为)称为“隐藏信息”。 通过组件内的信息隐藏行为,我们保证它只能通过外部组件间接访问(通过接口)。
这必然需要某种分组容器,其中某些更细粒度的功能可以被隐藏起来。
这就是我们为什么要“分组”的原因。
至于为什么我们使用类来分组的东西:
A)类提供了一种语言支持的封装机制。
B)我们不仅仅使用类:我们还使用名称空间/包进行封装。
问候,
埃德。
我可以想到两个原因。
可维护性:你自然会期望一些逻辑走到一起。 定义某个特定外部服务(例如数据库)操作的逻辑可能应该按逻辑方式组合在一起。 您可以在名称空间或类中执行此操作。
状态和身份:对象不仅包含逻辑,还保持状态。 作为与特定对象的状态一起工作的界面的一部分的逻辑应该在该对象上定义。 对象还维护身份,在问题域中模拟一个实体的对象应该是软件中的一个对象。
作为一个旁节点:状态和标识参数大多适用于域对象。 在大多数解决方案中,我主要将IoC容器用于这些服务。 我的域对象通常是作为程序流的一部分创建和销毁的,我通常为此使用单独的工厂对象。 工厂可以通过IoC容器注入和处理。 我已经成功地创建了工厂作为IoC容器的包装。 这样容器也可以处理域对象的生命周期。
这是一个非常有趣的问题。 如果我回顾过去实现过程的方式,我可以看到趋势是更小,更细化的接口和类。 事情变得更好了。 尽管如此,我认为最佳解决方案并不是每个班级都有一个功能。 这实际上意味着你将OO语言用作功能性语言,而功能性语言非常强大,但将这两种范式结合起来还有很多需要说明的地方。
链接地址: http://www.djcxy.com/p/14435.html上一篇: Going bananas with loose coupling and dependency injection
下一篇: Dependency Inversion Principle (SOLID) vs Encapsulation (Pillars of OOP)