OO дизайн и круговые зависимости
В настоящее время я борюсь с проблемой циклической зависимости при разработке моих классов.
С тех пор, как я прочитал о Anemic Domain Model (что-то, что я делал все время), я действительно пытался уйти от создания объектов домена, которые были просто "ведра геттеров и сеттеров" и вернитесь к моим корням OO.
Однако проблема ниже - это то, что я часто встречаю, и я не уверен, как я должен ее решить.
Скажем, у нас есть класс Team, у которого много Игроков. Не имеет значения, какой спорт это:) Команда может добавлять и удалять игроков, так же, как игрок может покинуть команду и присоединиться к другому.
Итак, у нас есть команда, в которой есть список игроков:
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, который имеет ссылку на команду:
public class Player {
private Team team;
public void leaveTeam() {
team = null;
// Do some more player stuff...
}
}
Можно предположить, что оба метода (удалить и оставить) имеют логику, специфичную для домена, которая должна выполняться всякий раз, когда команда удаляет игрока, а игрок покидает команду. Поэтому моя первая мысль заключается в том, что когда команда Team ударяет игрока, removePlayer (...) также следует вызвать метод player.leaveTeam()...
Но тогда, если Player ведет отъезд - должен ли метод leaveTeam() вызвать команду .removePlayer(это)? Не без создания бесконечного цикла!
В прошлом я бы просто сделал эти объекты "немыми" POJO и имел служебный уровень. Но даже сейчас я все еще остаюсь с этой проблемой: чтобы избежать круговых зависимостей, уровень сервиса все еще связывает все вместе - т.е.
public class SomeService {
public void leave(Player player, Team team) {
team.removePlayer(player);
player.leaveTeam();
}
}
Неужели я усложняю это? Возможно, мне не хватает очевидного недостатка в дизайне. Любая обратная связь будет с благодарностью.
Спасибо всем за ответы. Я принимаю решение Grodriguez, поскольку оно является наиболее очевидным (не могу поверить, что это не произошло со мной) и легко реализуется. Однако DecaniBass делает хороший момент. В ситуации, о которой я рассказывал, игрок может покинуть команду (и быть в курсе, находится ли он в команде или нет), а также команда, ведущая удаление. Но я согласен с вашей точкой зрения, и мне не нравится идея, что в этот процесс есть две "точки входа". Еще раз спасибо.
Ответы
Ответ 1
Вы можете разбить круговую зависимость, добавив охранников, чтобы проверить, есть ли у команды еще игрок/игрок все еще находится в команде. Например:
В классе 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
Бен,
Я бы начал с вопроса, может ли игрок (логически, юридически) удалить себя из команды. Я бы сказал, что объект игрока не знает, в какой команде он (!), Он является частью команды. Итак, удалите Player#leaveTeam()
и выполните все изменения команды с помощью метода Team#removePlayer()
.
В случае, когда у вас есть только игрок и его нужно удалить из своей команды, тогда у вас может быть статический метод поиска в Team public static Team findTeam( Player player ) ...
Я знаю, что это менее удовлетворительно и естественно, чем метод Player#leaveTeam()
, но по моему опыту вы все еще можете иметь значимую модель домена.
2 ссылки (родительский → ребенок и ребенок → родитель) часто чреваты другими вещами, например, сбор мусора, сохранение "ссылочной целостности" и т.д.
Дизайн - это компромисс!
Ответ 3
Идея состоит в том, чтобы делать вещи, связанные с доменами, в разных методах, которые не назовут друг друга, но делают вещи, относящиеся к доменам, для их собственного объекта, т.е. метод команды делает это для команды и игрока, делает это для игрока
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();
}
}
Ответ 4
public void removePlayer(Player player) {
if (players.contains(player)) {
players.remove(player);
player.leaveTeam();
}
}
Тоже внутри leaveTeam
.