将数据与逻辑分离
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" 是正确的名称吗?
我希望你能帮助解决一个问题,这个问题一次又一次地挑战着我,现在才能够将它表达成一个查询。
麦克
所有三种情况 都可以 使用,其中更好的选择取决于凝聚力。换句话说,它取决于 xp
与 User
的关系(无论是属性还是聚合域实体,甚至是属于其他域)。其他值如 lives
、health
等可以有不同的解决方案。因此,您不必对所有内容应用相同的方法,此外,我确信它们在您的领域模型中扮演着不同的角色。
1.高内聚(属性),值是父对象结构的一部分,没有它父对象就不可能存在。那么它应该是一个字段(属性)and/or一个方法集,就像你的第一个例子一样。
然而,xp
.
似乎并非如此
2。聚合实体,该值与父级(聚合根)高度相关但可以单独使用。这是 XPComponent
的第二个示例。因此,您可以决定 XPComponent
是否是您域的聚合实体。我在这里没有看到 Demeter 定律的大麻烦,因为它在更高级别的系统架构上有意义,see also。
3。单独的实体。 它只有对父实体的引用。这不是 xp
.
的情况
4。服务功能。 所以它既不是实体也不是领域模型的一部分。在这种情况下,您可以使用 Helpers
解决方案,它实际上是一个域服务。
假设我有一个具有 "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" 是正确的名称吗?
我希望你能帮助解决一个问题,这个问题一次又一次地挑战着我,现在才能够将它表达成一个查询。
麦克
所有三种情况 都可以 使用,其中更好的选择取决于凝聚力。换句话说,它取决于 xp
与 User
的关系(无论是属性还是聚合域实体,甚至是属于其他域)。其他值如 lives
、health
等可以有不同的解决方案。因此,您不必对所有内容应用相同的方法,此外,我确信它们在您的领域模型中扮演着不同的角色。
1.高内聚(属性),值是父对象结构的一部分,没有它父对象就不可能存在。那么它应该是一个字段(属性)and/or一个方法集,就像你的第一个例子一样。
然而,xp
.
2。聚合实体,该值与父级(聚合根)高度相关但可以单独使用。这是 XPComponent
的第二个示例。因此,您可以决定 XPComponent
是否是您域的聚合实体。我在这里没有看到 Demeter 定律的大麻烦,因为它在更高级别的系统架构上有意义,see also。
3。单独的实体。 它只有对父实体的引用。这不是 xp
.
4。服务功能。 所以它既不是实体也不是领域模型的一部分。在这种情况下,您可以使用 Helpers
解决方案,它实际上是一个域服务。