将数据与逻辑分离

Separating Data From Logic

假设我有一个具有 "xp" 值的用户对象。现在说我有一个 XPLevelsModel,它包含一个 XPLevels 数组,定义了该级别(除其他外)需要多少点:

class User
{       
    xp : int;
}

class XPLevelsModel
{
    levels : XPLevel;
}

class XPLevel
{
    ...
    xpRequired : int;
    name : String;
    ...
}

好的,现在说我有很多方法,例如 "getCurrentLevel()" "getPercentProgressThroughLevel()" 等。这些方法应该放在哪里?

我可以将它们放在用户对象上:

class User
{
    constructor(levelsModel:XPLevelsModel) : {}

    xp : int;

    getCurrentLevel() : int {}
    getPercentProgressThroughLevel() : Number {}
    ...
}

但随着我们添加更多数据,例如 "lives"、"health" 等等,用户将开始成长并拥有许多功能(对 SoC 来说不是很好)

我可以尝试将这些东西组合成组件,例如 "XPComponent" 这是 User:

上的一个对象
class User
{
    xp : UserXPComponent;
}

class UserXPComponent
{
    constructor(levelsModel:XPLevelsModel) {}
    points : int;

    getCurrentLevel() : int {}
    getPercentProgressThroughLevel() : Number {}
    ...
}

但问题是从游戏的其余部分访问此数据现在意味着 user.xp.getCurrentLevel(),这违反了 Demeter 的法则,而且它丑陋,难以测试,如果你走得更多会发生什么层次深?

我想到了另一种选择,比如:

class User
{
    xp : int 
}

class UserXPHelpers
{
    constructor(levelsModel:XPLevelsModel) {}

    getCurrentLevel(user:User) : int {}
    getPercentProgressThroughLevel(user:User) : Number {}
}

这里我们把逻辑和数据分离出来,通过方法传递给用户。唯一的问题是,这不会破坏封装吗?由于所有这些方法都依赖于传递给它们的用户,它们不应该与用户一起生活吗?

如果这是正确的解决方案,"Helpers" 是正确的名称吗?

我希望你能帮助解决一个问题,这个问题一次又一次地挑战着我,现在才能够将它表达成一个查询。

麦克

所有三种情况 都可以 使用,其中更好的选择取决于凝聚力。换句话说,它取决于 xpUser 的关系(无论是属性还是聚合域实体,甚至是属于其他域)。其他值如 liveshealth 等可以有不同的解决方案。因此,您不必对所有内容应用相同的方法,此外,我确信它们在您的领域模型中扮演着不同的角色。

1.高内聚(属性),值是父对象结构的一部分,没有它父对象就不可能存在。那么它应该是一个字段(属性)and/or一个方法集,就像你的第一个例子一样。

然而,xp.

似乎并非如此

2。聚合实体,该值与父级(聚合根)高度相关但可以单独使用。这是 XPComponent 的第二个示例。因此,您可以决定 XPComponent 是否是您域的聚合实体。我在这里没有看到 Demeter 定律的大麻烦,因为它在更高级别的系统架构上有意义,see also

3。单独的实体。 它只有对父实体的引用。这不是 xp.

的情况

4。服务功能。 所以它既不是实体也不是领域模型的一部分。在这种情况下,您可以使用 Helpers 解决方案,它实际上是一个域服务。