22

I am currently struggling with a circular dependency problem when designing my classes.

Ever since I read about the Anemic Domain Model (something I was doing all the time), I have really been trying to get away from creating domain objects that were just "buckets of getters and setters" and return to my OO roots.

However, the problem below is one that I come across a lot and I'm not sure how I should solve it.

Say we have a Team class, that has many Players. It doesn't matter what sport this is :) A team can add and remove players, in much the same way a player can leave a team and join another.

So we have the team, which has a list of 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
    }
}

Then we have the Player, which has a reference to the Team:

public class Player {
    private Team team;

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

One can assume that both methods (remove and leave) have domain-specific logic that needs to be run whenever a team removes a player and a player leaves a team. Therefore, my first thought is that when a Team kicks a player, removePlayer(...) should also call the player.leaveTeam() method...

But then what if the Player is driving the departure - should the leaveTeam() method call team.removePlayer(this)? Not without creating an infinite loop!

In the past, I'd have just made these objects "dumb" POJOs and had a service layer do the work. But even now I'm still left with that problem: to avoid circular dependencies, the service layer still has link it all together - i.e.

public class SomeService {

    public void leave(Player player, Team team) {

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

    }

}

Am I over complicating this? Perhaps I'm missing some obvious design flaw. Any feedback would be greatly appreciated.


Thanks all for the responses. I'm accepting Grodriguez's solution as it is the most obvious (can't believe it didn't occur to me) and easy to implement. However, DecaniBass does make a good point. In the situation I was describing, it is possible for a player to leave a team (and be aware of whether he is in a team or not) as well as the team driving the removal. But I agree with your point and I don't like the idea that there's two "entry points" into this process. Thanks again.

4

4 に答える 4

16

You can break the circular dependency by adding guards to check if the team still has the player / the player is still in the team. For example:

In class Team:

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

In class Player:

public void leaveTeam() {
    if (team != null)
    {
        team.removePlayer(this);
        team = null;
        // Do some more player stuff..
    }
}
于 2010-10-24T07:52:38.100 に答える
8

ベン、

まず、プレーヤーが(論理的、合法的に)チームから脱退できるかどうかを尋ねることから始めます。プレイヤー オブジェクトは、自分がどのチームに所属しているか (!) を認識していません。彼はチームの一員です。Player#leaveTeam()そのため、すべてのチームの変更を削除して、Team#removePlayer()メソッドを介して発生させます。

プレーヤーが 1 人しかなく、チームから削除する必要がある場合は、Team で静的ルックアップ メソッドを使用できます。public static Team findTeam( Player player ) ...

これは方法よりも満足度が低く、自然ではないことはわかっていPlayer#leaveTeam()ますが、私の経験では、それでも意味のあるドメイン モデルを作成できます。

2 方向の参照 (親 -> 子および子 -> 親) は、多くの場合、ガベージ コレクション、「参照整合性」の維持など、他のことでいっぱいです。

デザインは妥協!

于 2010-10-24T08:15:51.973 に答える
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();
    }

}
于 2010-10-24T08:03:17.563 に答える
1
public void removePlayer(Player player) {
    if (players.contains(player)) {
        players.remove(player);
        player.leaveTeam();
    }
}

Ditto inside leaveTeam.

于 2010-10-24T07:51:25.813 に答える