随着我们的依赖注入框架(春季注释)的最新成员,创建DI管理组件的边际成本似乎已达到一个关键的新门槛。虽然之前有一个与spring相关的开销(大量的XML和额外的间接性),依赖注入似乎已经开始进入许多模式;他们陷入困境并“消失”。
这样做的结果是与large number组件相关的概念开销变得可以接受。可以说,我们可以建立一个大多数类只暴露的系统 一个单一的公共方法,并通过像疯了一样聚合这些部分来构建整个系统。在我们的例子中,给出了一些东西;应用程序的用户界面具有一些功能要求,可以塑造最顶层的服务。后端系统控制下半部分。但是在这两者之间,一切都可以争夺。
我们经常讨论为什么我们要在课程中分组和原则应该?有几件事是肯定的;门面图案已经死亡并被埋葬。任何包含多个不相关功能的服务也往往会被拆分。 “无关特征”的解释比我之前所做的更为严格。
在我们的团队中,有两种流行的思路:实施依赖关系限制分组;单个类中的任何功能最好是所有注入依赖项的客户端。我们是一个DDD项目,另一个部分认为域限制分组(CustomerService或更细粒度的CustomerProductService,CustomerOrderService) - 注入依赖项的规范化使用是不重要的。
所以在松散耦合的DI宇宙中,我们为什么要在类中对逻辑进行分组?
编辑:duffymo指出这可能正朝着功能性的编程方式发展;这带来了国家问题。我们有很多“状态”对象代表(小)相关应用程序状态。我们将这些注入任何对此状态有合法需求的服务。 (我们使用“状态”对象而不是常规域对象的原因是spring在未指定的时间构造它们。我将此视为一种轻微的解决方法或替代解决方案,让spring管理域对象的实际创建。可能有更好的解决方案这里)。
因此,例如任何需要OrderSystemAccessControlState的服务都可以注入这个,并且消费者不容易知道这些数据的范围。一些安全相关状态通常用于许多不同的级别,但在中间的级别上完全不可见。我真的认为这从根本上违背了功能原则。我甚至很难从OO角度调整这个概念 - 但只要注入状态是精确且强类型,那么需要是合法的,用例也是正确的。
答案 0 :(得分:8)
良好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我在看你)和命名不好的实现类。
答案 1 :(得分:4)
您是否在强调“分组”或“类?”
如果你问我们为什么要把事情分组,那么我会将Medelt的第二部分称为“可维护性”,尽管我将其改为“减少涟漪效应的潜在成本。”
暂时考虑一下,不是组件(类,文件,它们可能是什么)之间的实际耦合,而是潜在的耦合,即这些组件之间可能存在的最大源代码依赖数。
有一个定理表明,给定一个分量链a - b - c - d - e,使得a取决于b等,改变e的概率将随后改变c分量不可能更大而改变e的可能性则会改变d。在真实的软件系统中,改变e将影响c的概率通常小于改变e将影响d的概率。
当然,你可能会说,这很明显。但我们可以使它更加明显。在这个例子中,d与e有直接依赖关系,c对e有间接(via d)依赖关系。因此,我们可以说,从统计上来说,主要由直接依赖形成的系统将比主要由间接依赖形成的系统遭受更大的连锁反应。
鉴于在现实世界中,每次涟漪效应都需要花钱,我们可以说,主要由直接依赖性构成的系统更新的连锁反应成本将高于系统更新的连锁效应成本主要由间接依赖形成。
现在,回到潜在的耦合。可以证明,在绝对封装上下文(例如Java或C#,其中没有广泛采用递归封装)中,所有组件可能通过直接依赖或与单个中间组件的间接依赖相互连接。从统计上来说,一个最小化其组件之间直接电位耦合的系统可以最大限度地降低由于任何更新而产生的连锁反应的潜在成本。
我们如何在直接和间接潜在耦合之间实现这种区分(好像我们还没有让猫从袋子里出来)?封装。
封装是指模型化实体中包含的信息只能通过该建模实体支持的接口上的交互来访问的属性。不能通过这些接口访问的信息(可以是数据或行为)称为“信息隐藏”。通过组件内的信息隐藏行为,我们保证只能通过外部组件间接(通过接口)访问它。
这必然需要某种分组容器,其中某些细粒度的功能可以被信息隐藏。
这就是为什么我们是“分组”的原因。
至于为什么我们使用类来分组:
A)类提供了一种语言支持的封装机制。
B)我们不只是使用类:我们还使用命名空间/包进行封装。
此致
版
答案 2 :(得分:3)
我可以想到两个原因。
可维护性:你自然会期望一些逻辑在一起。定义一个特定外部服务(例如数据库)上的操作的逻辑应该以逻辑方式组合在一起。您可以在命名空间或类中执行此操作。
状态和标识:对象不仅包含逻辑,还维护状态。应该在该对象上定义作为处理特定对象状态的接口的一部分的逻辑。对象也保持身份,对问题域中的一个实体进行建模的对象应该是软件中的一个对象。
作为边节点:state和identity参数主要适用于域对象。在大多数解决方案中,我主要使用IoC容器来处理这些服务。我的域对象通常作为程序流的一部分创建和销毁,我通常使用单独的工厂对象。然后可以通过IoC容器注入和处理工厂。我已经成功创建工厂作为IoC容器周围的包装器。这样容器也可以处理域对象的生命周期。
这是一个非常有趣的问题。如果我回顾过去我实现过程的方式,我可以看到更小,更精细的接口和类的趋势。事情肯定会变得更好。我不认为最佳解决方案每个类有一个功能。这实际上意味着你使用OO语言作为函数式语言,而功能语言非常强大,结合这两种范式还有很多可以说的。
答案 3 :(得分:2)
成本因素
希望我的新手(是的,我充满了言语)与DI并没有让我完全错误。