单一职责原则如何与贫血/富领域模型相关?

10

最近我在检查从另一个团队接手的代码,对于如何应用SRP及其与贫血或富领域模型(由Martin Fowler定义)的关系有疑问。

富领域模型的概念是拥有智能对象,这些对象不仅可以设置/获取它们的属性,还可以执行一些更复杂的业务逻辑。我想知道它如何符合SRP?

假设我的模型类具有一些属性,可以公开这些属性并在其属性上提供一些简单的计算。下一个要求是将此对象数据存储在某个不受我控制的存储对象中,像这样:

class MyObject {
    // get set
    // parse sth
    
}

将方法存储在存储器中

   storage.store(key, object);

如果MyObject有如下存储方法,是否违反了SRP原则?

public void store(Storage storage) {
    storage.store('keyOne', fieldOne);
    storage.store('keyTwo', fieldTwo);
}

从这个对象的角度来看,能够存储它的状态是一件好事情。 另一种方式可能是在这里引入某种服务,并像这样进行:

public StorageService {
    private Storage;
    // constructor here
    ....
    public void store(MyObject myobj);
}

你能给我指出一些关于这个问题的阅读链接吗?我在这里找到了一个 SO 的帖子,但它并没有完全回答我的问题。

在 DDD 中该如何解决这个问题? 在 DDD 中,模型从定义上来说是丰富的,并且可以被看作具有过多的职责。


3
这可能是对SRP过于字面理解了。忽略Bob大叔,而要追求“内聚性”。 - Tom Hawtin - tackline
正如我在答案中所指出的,如果Storage是稳定和抽象的(比如一些标准的JSON、XML、RDB接口),那么,在我看来,完全没有问题去追求内聚性并将其放入领域模型中。 - user949300
3个回答

7
一个丰富的领域模型(RDM)意味着控制模型行为的逻辑属于模型本身,而不是将模型视为具有getter/setter的数据。这并不意味着包括持久性、安全性、如何在GUI中显示模型等所有内容都需要在模型内部。
RDM和SRP是相辅相成的,它们并不冲突。
违反SRP/RDM:
Car {
   // possibly violates SRP
   storeInDatabase();  
   // smells like anemic domain model
   getEngineState();   
}

遵循 SRP/RDM 原则:

// usings aspects to remove cross-cutting concerns from the model and follow SRP
@DatabaseSerializable 
Car {
   // rich domain model encapsulates engine state and exposes behavior
   drive();            
}

1
如果你的汽车除了Drive()之外还有许多其他方法,例如SwitchOn()、SwitchOff()、Turn()、Brake()、PlanRoute()等,那么你确实拥有RDM,但你并没有SRP。类的行为越丰富,它就越不符合SRP原则。这些概念是相互对立的。 - Marius George
RDM意味着你的模型应该包含领域逻辑。SRP意味着你的类应该只负责一件事情——在模型的情况下,这意味着“包含领域逻辑”,而不是包含序列化代码或打开Web连接等代码。 - Garrett Hall
我了解这些含义。正如JuanZe所说:“在DDD中,模型的定义是丰富的,可以被视为具有过多职责”。换句话说,如果一个实体不具备多个职责(超过1个),那么要实现一个真正丰富的实体并不容易。当然,除非我们将单一职责的服务/对象注入其中,否则它就处于边缘ADM状态。 - Marius George

4
"DDD中的模型在定义上是丰富的,可以被视为拥有过多职责"这个解释对DDD来说是过于简单化了。实际上,这要看你的模型设计得有多好。你可能会使用DDD创建糟糕的模型(例如创建具有过多职责的对象或创建贫血模型)。DDD和SRP都是很好的实践,同时重构、TDD等也是如此,但你应该根据自己的经验和判断力(或他人的意见)来补充它们的使用。每种实践都有其优缺点,不要教条地应用任何一种实践。"

3

@Garrett Hall

我有些不同意你的说法:“RDM和SRP是相辅相成的,它们并不矛盾。” 根据我的经验,当SRP被过分强调时,会导致贫血的领域模型。 "不,我们不能做或甚至无法支持任何持久化操作,不,我们不能做21-CFR11,不,我们甚至不知道GUI是什么..." 这样你的类最终就没有做任何事情,只有一个贫血的领域模型。

如果过分强调RDM(这是我倾向于犯的方向/错误),那么SRP完全会被忽视,你最终会发现你的类有数百个方法,显然做了太多的事情。

你需要找到一个平衡点,即同时考虑RDM和SRP的中间点。找到这种平衡很难,通常涉及团队内部的直觉和政治,而不是技术能力或规则。

“认识自己”。如果你像我一样倾向于过于复杂的类,请注意。当你看到别人的类甚至连你都觉得太复杂时,这是一个很大的警告信号。同样地,如果你知道自己对SRP非常严格,而你看到一个类甚至连你的标准都显得贫血,那就是一个重要的代码气味。

现在,略微回答OP关于存储的问题,我认为很大程度上取决于存储的稳定性、标准性和抽象性。如果存储是一些标准的XML、CSV或RDB抽象,我完全没有问题让对象知道如何存储自己。


SRP原则指出,一个类应该只有一个变化的原因,也就是说只有一个变化的来源。因此,当一个对象存在不同的行为时,它们应该被分离成不同的类,并且这些类(在DDD中称为服务)属于领域模型。 - Amir Pashazadeh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接