域模型类是不是应该始终依赖于原语?

Posted

技术标签:

【中文标题】域模型类是不是应该始终依赖于原语?【英文标题】:Should Domain Model Classes always depend on primitives?域模型类是否应该始终依赖于原语? 【发布时间】:2021-02-21 11:21:18 【问题描述】:

Architecture Patterns with Python 进行到一半时,我有两个关于如何构建和实例化领域模型类的问题。假设在我的域模型上我有 DepthMap 类:

class DepthMap:
    def __init__(self, map: np.ndarray):
        self.map = map

根据我从书中的理解,这个类是不正确的,因为它依赖于Numpy,它应该只依赖于 Python 原语,因此问题是:领域模型类是否应该只依赖于 Python 原语,还是有例外?

假设上一个问题的答案是类应该完全依赖于原语,那么从 Numpy 数组创建 DepthMap 的正确方法是什么?假设现在我有更多格式可以制作 DepthMap 对象。

class DepthMap:
    def __init__(self, map: List):
        self.map = map
    
    @classmethod
    def from_numpy(cls, map: np.ndarray):
        return cls(map.tolist())

    @classmethod
    def from_str(cls, map: str):
        return cls([float(i) for i in s.split(',')])

或工厂:

class DepthMapFactory:
    @staticmethod
    def from_numpy(map: np.ndarray):
        return DepthMap(map.tolist())

    @staticmethod
    def from_str(map: str):
        return DepthMap([float(i) for i in s.split(',')])

我认为即使是他们在书中提到的Repository Pattern,也可以放在这里:

class StrRepository:
    def get(map: str):
        return DepthMap([float(i) for i in s.split(',')])

class NumpyRepository:
    def get(map: np.ndarray):
        return DepthMap(map.tolist())

第二个问题:从不同来源创建领域模型对象时,正确的做法是什么?

注意:我的背景不是软件;因此某些 OOP 概念可能不正确。不要投反对票,请发表评论并让我知道如何改进问题。

【问题讨论】:

【参考方案1】:

我认为依赖纯语言扩展的库是完全可以的,否则你最终将不得不定义大量的“接口契约”(Python 没有接口作为语言结构——但那些可以是概念性的)抽象出这些数据结构,最终那些新引入的合约可能无论如何都可能是糟糕的抽象,只会导致额外的复杂性。

这意味着您的域对象通常可以依赖于这些纯类型。另一方面,我也认为这些类型应该被视为语言“原语”(本机可能更准确),就像datetime 一样,您应该避免使用primitive obsession。

换句话说,DepthMap 是一个域概念,它的构造允许依赖于Numpy(这里不需要抽象),但不一定允许Numpy 深入到域中(除非它是适当的抽象)。

或者在伪代码中,这可能很糟糕:

someOperation(Numpy: depthMap);

这可能更好:

class DepthMap(Numpy: data);
someOperation(DepthMap depthMap);

关于第二个问题,从 DDD 的角度来看,如果 DepthMap 类有一个 Numpy 数组,因为它是内部结构,但必须 从其他来源(例如字符串或列表)构建 最好的方法是存储库模式?或者这只是为了 处理数据库和工厂是更好的方法?

存储库模式专门用于存储/检索,因此不合适。现在,您可能在DepthMap 上直接有一个接受Numpy 的工厂方法,或者您可能有一个专用工厂。如果你想将DepthMapNumpy 解耦,那么引入一个专门的工厂可能是有意义的,但乍一看似乎没有必要。

【讨论】:

FWIW,我认为这个答案是正确的,但我没有足够的声誉来评论和回答后续问题,所以不得不发布一个新的。恕我直言,这应该是公认的答案。 @bobgregory 谢谢!你的回答同样好,所以不要因为你的恶名偷走我的名声而感到难过哈哈 :)。此外,很高兴看到作者花时间为 SO 等其他知识流做出贡献。干杯!【参考方案2】:

领域模型类是否应该仅依赖 Python 原语

纯粹从领域驱动设计的角度来说,绝对没有理由认为这是真的

您的领域动态通常将使用您领域的语言来描述,即实体和价值对象的操作(Evans,2003 年),它们是将领域语义置于数据结构之上的外观。

幕后的底层数据结构是完成工作所需的一切。

领域驱动设计中没有什么要求您放弃高度优化的 Bazzlefraz 的经过良好测试的现成实现,而是从头开始编写自己的实现。

领域驱动设计的部分意义在于,我们希望将投资投入到有助于业务发展的代码上,而不是管道。

【讨论】:

【参考方案3】:

我写了这本书,所以我至少可以回答你的问题。

您可以在域模型中使用原语(str、int、boolean 等)以外的东西。通常,虽然我们无法在书中展示,但您的模型类将包含整个对象层次结构。

您要避免的是您的技术实现以一种难以表达您的意图的方式泄漏到您的代码中。在你的代码库中传递 Numpy 数组的实例可能是不合适的,除非你的域是 Numpy。我们试图通过将有趣的东西与胶水分开来使代码更易于阅读和测试。

为此,您可以拥有一个公开某些行为的 DepthMap 类,并且恰好有一个 Numpy 数组作为其内部存储。这与您使用库中的任何其他数据结构没有什么不同。

如果您将数据作为平面文件或其他东西获取,并且创建 Numpy 数组涉及复杂的逻辑,那么我认为工厂是合适的。这样一来,您就可以将用于生成 DepthMap 的无聊、丑陋的代码保留在系统的边缘,而不是您的模型。

如果从字符串创建 DepthMap 确实是单行的,那么类方法可能会更好,因为它更容易找到和理解。

【讨论】:

以上是关于域模型类是不是应该始终依赖于原语?的主要内容,如果未能解决你的问题,请参考以下文章

Java - 为啥 char 不应该被隐式转换为字节(和短)原语?

数据模型

Scheme 中是不是有与 Lisp 的“运行时”原语等价的东西?

Windows中的原语与原子

Java原语可以被视为轻对象吗?

Vbo如何绘制原语