Nia*_*ton 58 oop solid-principles
我正处于一个非常重视单一责任原则的项目中.我们有很多小班,事情很简单.但是,我们有一个贫血的领域模型 - 我们的任何模型类都没有行为,它们只是属性包.这不是对我们设计的抱怨 - 它实际上看起来效果很好
在设计评审期间,只要将新行为添加到系统中,SRP就会被引出,因此新行为通常会在新类中结束.这使得事情很容易被单元测试,但我有时会感到困惑,因为它感觉就像将行为拉出相关的地方.
我正在努力提高我对如何正确应用SRP的理解.在我看来,SRP反对添加与一个对象共享相同上下文的业务建模行为,因为该对象不可避免地最终要么做多个相关的事情,要么做一件事但是要知道改变形状的多个业务规则其产出.
如果是这样,那么感觉最终结果是一个贫血领域模型,这在我们的项目中肯定是这样.然而,贫血领域模型是一种反模式.
这两个想法可以共存吗?
编辑:一些上下文相关的链接:
SRP - http://www.objectmentor.com/resources/articles/srp.pdf
贫血领域模型 - http://martinfowler.com/bliki/AnemicDomainModel.html
我不是那种喜欢找先知并遵循他们所说的福音的开发者.所以我没有提供这些链接,作为说明"这些是规则"的方式,只是作为两个概念的定义来源.
Cor*_*son 13
富域模型(RDM)和单一责任原则(SRP)不一定是不一致的.RDM与SRP的一个非常专业的子类更加不一致 - 该模型主张"数据bean +控制器类中的所有业务逻辑"(DBABLICC).
如果您阅读了Martin的SRP章节,您将看到他的调制解调器示例完全在域层中,但将DataChannel和Connection概念抽象为单独的类.他将调制解调器本身保留为包装器,因为这对客户端代码是有用的抽象.它更多地是关于正确(重新)分解而不仅仅是分层.内聚和耦合仍然是设计的基本原则.
最后,三个问题:
正如马丁自己所说,要看到不同的"改变原因"并不总是那么容易.YAGNI,Agile等的概念阻碍了对未来变化原因的预期,因此我们不应该发明那些不是立即显而易见的原因.我认为"过早的,预期的变革原因"是应用SRP 的真正风险,应该由开发人员管理.
除此之外,甚至SRP的正确(但不必要的肛门)应用可能导致不必要的复杂性.总是想想下一个必须维护你的课程的可怜的草皮:将琐碎的行为勤奋地抽象到自己的界面,基类和单行实现中是否真的有助于他理解应该只是一个单独的类?
软件设计通常是在竞争力量之间达成最佳折衷.例如,分层体系结构主要是SRP的一个很好的应用程序,但是,例如,将业务类的属性从布尔值更改为枚举的事实会在所有层中产生连锁反应 - 从数据库到域名,外观,网络服务,再到GUI?这是否意味着糟糕的设计?不一定:它指的是这样一个事实,即你的设计倾向于改变另一个方面.
我必须说"是",但你必须正确地做你的SRP.如果相同的操作只适用于一个类,那么它属于那个类,你不是吗?如果相同的操作适用于多个类怎么样?在这种情况下,如果你想遵循组合数据和行为的OO模型,你会把操作放到基类中,不是吗?
我怀疑从你的描述中,你最终会得到基本上是操作包的类,所以你基本上重新创建了C风格的编码:结构和模块.
来自链接的SRP文件:" SRP是最简单的原则之一,也是最难做到的. "
SRP论文的引用非常正确; SRP很难做对.这个和OCP是SOLID的两个元素,它们必须至少在一定程度上放松才能真正完成项目.过度使用任何一种都会很快产生馄饨代码.
如果"改变的原因"过于具体,SRP确实可以被视为荒谬的长度.如果您认为字段的类型更改为"更改",即使POCO/POJO"数据包"也可以被视为违反SRP.您认为常识会告诉您字段的类型更改是"更改"的必要条件,但我已经看到了包含内置值类型包装的域图层; 一个让ADM看起来像乌托邦的地狱.
基于可读性或期望的凝聚力水平,基于一些现实的目标为自己奠定基础通常是好的.当你说"我希望这个班级做一件事"时,它应该没有或多于做必要的事情.你可以用这个基本哲学保持至少程序上的凝聚力."我希望这个类能够维护发票的所有数据"通常会允许一些业务逻辑,甚至总结小计或计算销售税,这取决于对象有责任知道如何为任何字段提供准确的,内部一致的值它包含.
我个人对"轻量级"域名没有大问题.仅具有作为"数据专家"的角色使得域对象成为与该类相关的每个字段/属性的守护者,以及所有计算的字段逻辑,任何显式/隐式数据类型转换,以及可能更简单的验证规则(即必填字段,值限制,如果允许则会在内部破坏实例的内容).如果计算算法(可能是加权平均值或滚动平均值)可能会发生变化,则封装算法并在计算字段中引用它(这只是好的OCP/PV).
我不认为这样的域对象是"贫血的".我对这个术语的看法是一个"数据包",这是一个对外界没有任何概念的领域集合,甚至是除了包含它们之外的其他领域之间的关系.我也看到了这一点,追踪对象永远不会知道的对象状态不一致并不是一件有趣的事情.过度热情的SRP将通过声明数据对象不对任何业务逻辑负责来导致这一点,但常识通常会首先介入,并说该对象作为数据专家必须负责维护一致的内部状态.
再次,个人意见,我更喜欢Repository模式到Active Record.一个对象,只有一个责任,并且在该层之上的系统中的其他任何东西都必须知道它是如何工作的.Active Record要求域层至少知道有关持久性方法或框架的一些特定细节(无论是用于读/写每个类的存储过程的名称,特定于框架的对象引用,还是用ORM信息装饰字段的属性) ),因此默认情况下注入第二个原因来更改为每个域类.
我的0.02美元.
我发现遵循坚实的原则实际上让我远离了 DDD 的丰富域模型,最后,我发现我不在乎。更重要的是,我发现域模型的逻辑概念和任何语言的类都不是 1:1 映射的,除非我们谈论的是某种外观。
我不会说这完全是一种具有结构和模块的 c 风格的编程,而是说你最终可能会得到更多功能性的东西,我意识到风格相似,但细节有很大的不同。我发现我的类实例最终表现得像高阶函数、偏函数应用、惰性求值函数或上述的某种组合。这对我来说有些难以言喻,但这就是我从遵循 TDD + SOLID 编写代码中得到的感觉,它最终表现得像一种混合 OO/Functional 风格。
至于继承是一个坏词,我认为这更多是因为在 Java/C# 等语言中继承的粒度不够细。在其他语言中,这不是问题,而是更有用。