【问题标题】:OO design and circular dependenciesOO设计和循环依赖
【发布时间】:2011-04-29 18:40:08
【问题描述】:

我目前在设计课程时遇到循环依赖问题。

自从我读到Anemic Domain Model(我一直在做的事情)以来,我一直在努力摆脱创建只是“getter 和 setter 桶”的域对象,并回到我的 OO 根源.

但是,下面的问题是我经常遇到的问题,我不知道应该如何解决。

假设我们有一个 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 确实是一个很好的观点。在我所描述的情况下,一名球员可能会离开一支球队(并注意他是否在一支球队中)以及推动移除的球队。但我同意你的观点,我不喜欢这个过程有两个“入口点”。再次感谢。

【问题讨论】:

    标签: java oop circular-dependency


    【解决方案1】:

    想法是在不同的方法中做与领域相关的事情,这些方法不相互调用,而是为自己的对象做领域相关的事情,即团队的方法为团队做,玩家的方法为玩家做

    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();
        }
    
    }
    

    【讨论】:

    • leaveTeam() 方法会在设置 team = null 后调用 team.removePlayerFromTeam() 时抛出 NPE。
    • 同样在这个解决方案中,调用player.leaveTeam()实际上并没有从团队对象中的玩家列表中删除玩家。同样,调用team.removePlayer() 不会将播放器对象中的team var 设置为null
    • 我认为,在这个设计中,包含特定领域代码的方法应该是包私有的而不是公共的。但这绝对是我要走的路。
    • @Waldheinz:我同意,如果有可能,这是最好的方法
    【解决方案2】:
    public void removePlayer(Player player) {
        if (players.contains(player)) {
            players.remove(player);
            player.leaveTeam();
        }
    }
    

    leaveTeam 内同上。

    【讨论】:

      【解决方案3】:

      你可以通过添加守卫来打破循环依赖来检查球队是否还有球员/球员是否还在球队中。例如:

      在课堂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..
          }
      }
      

      【讨论】:

      • 可能只有我一个人,但我喜欢尽可能少用 if...else。我注意到它使代码的可维护性降低了一些
      • players.remove() 如果集合被更改,将返回 true;无需执行 .contains()。
      • @KarlP:我知道,但我认为显式检查会使逻辑更清晰。
      • 第一个代码示例中 if 语句的右括号丢失了。
      • @Lie Ryan:这是一个多对多关系。一种建模方法是找出一个新的逻辑实体,该实体具有来自球员和球队的一对多关系。在当前示例中,它可能与存储玩家参加过的所有球队的历史数据有关。在这种情况下,它可能是“合同”:-)
      【解决方案4】:

      本,

      我会首先询问一名球员是否可以(从逻辑上、合法地)将自己从球队中移除。我会说玩家对象不知道他在哪个团队(!),他是团队的一部分。因此,删除Player#leaveTeam() 并通过Team#removePlayer() 方法使所有团队更改发生。

      如果您只有一名球员并且需要将其从球队中移除,那么您可以在球队public static Team findTeam( Player player ) ...上使用静态查找方法

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

      2 路引用(Parent -> Child 和 Child-> Parent)通常充满其他东西,比如垃圾收集、维护“引用完整性”等。

      设计是一种妥协!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-11-19
        • 2020-10-27
        • 2020-09-25
        • 1970-01-01
        • 2010-11-04
        • 2020-11-18
        • 2016-05-24
        相关资源
        最近更新 更多