OO设计与循环依赖问题

22

我目前在设计类时遇到了循环依赖的问题。

自从我阅读了贫血领域模型(我之前一直在这么做),我一直在努力摆脱创建那些只是“getter和setter桶”的领域对象并重回面向对象的本质。

然而,下面的问题是我经常遇到的,我不确定该如何解决它。

假设我们有一个团队(Team)类,它拥有许多球员(Players)。无论这是什么运动 :) 一个团队可以添加和删除球员,就像一个球员可以离开一支队伍并加入另一支队伍一样。

因此,我们有了一个具有球员列表的团队:

public class Team {

    private List<Player> players;

    // snip.

    public void removePlayer(Player player) {
        players.remove(player);
        // Do other admin work when a player leaves
    }
}

接下来是Player,它引用了Team:

public class Player {
    private Team team;

    public void leaveTeam() {
        team = null;
        // Do some more player stuff...
    }
}

可以假设两种方法(删除和离开)都有专门的领域逻辑,需要在团队移除玩家或者玩家离开团队时运行。因此,我的第一个想法是当 Team 踢出一名玩家时,removePlayer(...) 应该也调用 player.leaveTeam() 方法...

但是,如果是Player驱动离开团队呢?leaveTeam()方法是否应该调用team.removePlayer(this)?这样就会创建一个无限循环!

过去,我只是让这些对象成为“愚蠢”的POJO,并让服务层来完成工作。但现在我仍然面临着这个问题:为了避免循环依赖,服务层仍然需要将所有东西连接起来 - 即

public class SomeService {

    public void leave(Player player, Team team) {

        team.removePlayer(player);
        player.leaveTeam();

    }

}

我是不是把这个问题想得太复杂了?也许我错过了一些明显的设计缺陷。非常感谢任何反馈。


感谢所有的回答。我接受 Grodriguez 的解决方案,因为它最为明显(难以相信我没有想到)并且易于实现。然而,DecaniBass 也提出了一个很好的观点。在我描述的情况下,球员有可能离开一个团队(并且知道他是否在团队中),同时团队也可能驱逐球员。但我同意你的观点,我不喜欢这个过程有两个“入口”的想法。再次感谢。

4个回答

16

你可以通过添加保护条件来打破循环依赖,以检查团队是否仍然拥有该球员/该球员是否仍在团队中。例如:

在类Team中:

public void removePlayer(Player player) {
    if (players.contains(player))
    {
        players.remove(player);
        player.leaveTeam();
        // Do other admin work when a player leaves
    }
}

Player 类中:

public void leaveTeam() {
    if (team != null)
    {
        team.removePlayer(this);
        team = null;
        // Do some more player stuff..
    }
}

2
也许只有我这样想,但我尽可能地少使用if...else。我注意到这会使代码变得稍微难以维护。 - Gaurav Saxena
5
如果集合被更改,players.remove()将返回true;无需进行.contains()检查。 - KarlP
@KarlP:我知道,但我认为显式检查会使逻辑更清晰。 - Grodriguez
第一个代码示例中的if语句的右括号缺失。 - Core Xii
2
@Lie Ryan:这是一种多对多关系。建模的一种方法是找出一个新的逻辑实体,该实体具有从球员和团队中的每个人到多个关系。在当前示例中,存储球员曾经效力过的所有团队的历史数据可能是相关的。在这种情况下,它可能是一个“合同” :-) - KarlP
显示剩余2条评论

8

Ben,

首先,我想问一下球员是否可以(在逻辑和法律上)退出球队。我认为球员对象不知道他在哪个团队中,他只是团队的一部分。因此,删除Player#leaveTeam()方法,并通过Team#removePlayer()方法进行所有团队更改。

在只有一个球员并且需要将其从团队中移除的情况下,您可以在Team上拥有一个静态查找方法:public static Team findTeam( Player player ) ...

我知道这比Player#leaveTeam()方法更少令人满意和自然,但根据我的经验,您仍然可以拥有有意义的领域模型。

双向引用(父->子和子->父)通常会涉及其他问题,例如垃圾回收、维护“引用完整性”等。

设计是一种妥协!


2

该思路是以不相互调用的方式进行与领域相关的工作,每种方法均为其各自的对象(即团队和球员)执行领域相关的操作。

public class Team {

    private List<Player> players;

    public void removePlayer(Player player) {
        removePlayerFromTeam(player);
        player.removeFromTeam();
    }
    public void removePlayerFromTeam(Player player) {
        players.remove(player);
        //domain stuff
    }
}

public class Player {
    private Team team;

    public void removeFromTeam() {
         team = null;
        //domain stuff
    }
    public void leaveTeam() {
        team.removePlayerFromTeam(this);
        removeFromTeam();
    }

}

1
leaveTeam() 方法会抛出 NPE,因为您在将 team = null 赋值之后调用了 team.removePlayerFromTeam() - Grodriguez
在这个解决方案中,调用player.leaveTeam()并不会真正从团队对象的玩家列表中删除玩家。同样,调用team.removePlayer()也不会在玩家对象中将team变量设置为null - Grodriguez
1
在这个设计中,包含特定于领域的代码的方法应该是包私有的,而不是公共的。我认为这绝对是我会采取的路线。 - Waldheinz
@Waldheinz:我同意,如果有可能的话,这是最好的方式。 - Gaurav Saxena

1
public void removePlayer(Player player) {
    if (players.contains(player)) {
        players.remove(player);
        player.leaveTeam();
    }
}

leaveTeam 内部也一样。


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