ОО дизайн и круговые зависимости - PullRequest
19 голосов
/ 24 октября 2010

В настоящее время я борюсь с проблемой циклической зависимости при разработке своих классов.

С тех пор, как я прочитал о Анемичной Доменной Модели (чем-то, что я делал все время), я действительно пытался уйти от создания доменных объектов, которые были просто "блоками геттеров и сеттеров" и вернись к моим ОО-корням.

Однако проблема, с которой я столкнулся, часто встречается, и я не уверен, как мне ее решить.

Скажем, у нас есть класс 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
    }
}

Тогда у нас есть Игрок, который имеет ссылку на Команду:

public class Player {
    private Team team;

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

Можно предположить, что оба метода (удалить и уйти) имеют специфическую для домена логику, которую необходимо запускать всякий раз, когда команда удаляет игрока, а игрок покидает команду. Поэтому моя первая мысль - когда Team пинает игрока, removePlayer (...) также должен вызывать метод player.leaveTeam () ...

Но что, если Player управляет отъездом - должен ли метод leftTeam () вызывать team.removePlayer (this)? Не без создания бесконечного цикла!

В прошлом я бы просто сделал эти объекты "тупыми" POJO и сделал бы работу сервисным слоем. Но даже сейчас я все еще остаюсь с этой проблемой: чтобы избежать циклических зависимостей, сервисный уровень по-прежнему связывает все это вместе, т.е.

public class SomeService {

    public void leave(Player player, Team team) {

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

    }

}

Я слишком усложняю это? Возможно, мне не хватает какого-то очевидного недостатка дизайна. Любая обратная связь будет принята с благодарностью.

<Ч />

Спасибо всем за ответы. Я принимаю решение Grodriguez , так как оно является наиболее очевидным (не могу поверить, что оно мне не пришло в голову) и простым в реализации. Тем не менее, DecaniBass имеет смысл. В ситуации, которую я описывал, игрок может покинуть команду (и знать, находится ли он в команде или нет), а также команду, управляющую удалением. Но я согласен с вашей точкой зрения, и мне не нравится идея, что в этом процессе есть две «точки входа». Еще раз спасибо.

Ответы [ 4 ]

14 голосов
/ 24 октября 2010

Вы можете нарушить круговую зависимость, добавив охранников, чтобы проверить, есть ли в команде игрок / игрок все еще в команде.Например:

В классе 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..
    }
}
8 голосов
/ 24 октября 2010

Бен,

Я бы начал с вопроса, может ли игрок (по логике, по закону) удалить себя из команды. Я бы сказал, что объект игрока не знает, в какой команде он (!), Он является частью команды. Итак, удалите Player#leaveTeam() и внесите все изменения в команду с помощью метода Team#removePlayer().

В случае, когда у вас есть только игрок, и вам нужно удалить его из своей команды, вы можете использовать статический метод поиска в команде public static Team findTeam( Player player ) ...

Я знаю, что это менее удовлетворительно и естественно, чем Player#leaveTeam() метод, но по моему опыту у вас все еще может быть содержательная модель предметной области.

Двусторонние ссылки (Parent -> Child и Child-> Parent) часто чреваты другими вещами, например сборкой мусора, поддержанием "ссылочной целостности" и т. Д.

Дизайн - это компромисс!

2 голосов
/ 24 октября 2010

Идея состоит в том, чтобы выполнять связанные с доменом вещи в различных методах, которые не вызывают друг друга, но выполняют связанные с доменом вещи для своего собственного объекта, т.е. метод команды делает это для команды, а метод игрока делает это для игрока

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 голос
/ 24 октября 2010
public void removePlayer(Player player) {
    if (players.contains(player)) {
        players.remove(player);
        player.leaveTeam();
    }
}

То же самое внутри leaveTeam.

...