Java 如何安全地创建相互依赖的对象?

Java 如何安全地创建相互依赖的对象?,java,Java,假设我从以下两个类开始: public class Game { private final Player self; private final Player opponent; public Game(final Player self, final Player opponent) { this.self = Objects.requireNonNull(self); this.opponent = Objects.requireN

假设我从以下两个类开始:

public class Game {
    private final Player self;
    private final Player opponent;

    public Game(final Player self, final Player opponent) {
        this.self = Objects.requireNonNull(self);
        this.opponent = Objects.requireNonNull(opponent);
    }
}

现在我发现我需要访问我的
Player
类中的其他玩家(从而访问
Game
对象)

import javax.annotation.Nonnull;

public class Q23726363B
{
    public static void main(final String[] args)
    {
        final Game game = new Game(args[0], args[1]);
    }

    public static class Game
    {
        private final Player p1;
        private final Player p2;

        public Game(@Nonnull final String p1, @Nonnull final String p2)
        {
            this.p1 = new Player(p1);
            this.p2 = new Player(p2);
        }

        public class Player
        {
            private final String name;

            private Player(@Nonnull final String name) {this.name = name;}

            public Game getGame() { return Game.this; }
        }
    }
}
一种方法是,将其添加到
Player
类中:

    private Game game;

    public void setGame(final Game game) {
        this.game = Objects.requireNonNull(game);
    }
然而现在它打破了
游戏
对象的不变性,有没有办法保持相互创建的对象的不变性

或者我需要求助于手动施加的可变性和安全性(从常规客户端的角度,而不是从多线程同步的角度)?例如,每当有人尝试多次
setGame
时抛出异常

总而言之,这就是我试图解决的相互依赖:

Player playerSelf = new Player(/* non-existing game */, "Self");
Player playerOpponent = new Player(/* non-existing game */, "Opponent");
Game game = new Game(playerSelf, playerOpponent);

Game game = new Game(/* non-existing player */, /* non-existing player */);
Player playerSelf = new Player(game, "Self");
Player playerOpponent = new Player(game, "Opponent");

是否存在一种模式,例如有助于防止构造函数参数爆炸的构建器模式,如果想要避免在不使用构建器模式的情况下暴露,可以通过打破不变性的方式来解决这种模式?

每当您有循环依赖时,就打破它。它将有助于减少代码的耦合,提高可测试性,并使您保持理智。为什么
Player
首先需要访问其他玩家?您可能试图在其中添加太多功能。也许您可以将其移动到
游戏中
?或者,比如说,插入到
播放器
对象中的
策略

此外,请记住,不变性并不总是答案。有些事情,比如游戏状态,本质上是可变的。试图将它们硬塞进不可变的对象中必然会让生活变得悲惨。

不可变是一个伟大的目标,但在Java中100%不可变确实很难: Erlang使每个数据结构都是不可变的,当语言在该级别支持它时,这是非常好的。不幸的是,Java并没有在它需要的级别上支持这一点,以使它变得轻松和无痛

也就是说,这种施工顺序有多种解决方案: 遵循类似于MVC模式的方法,即
游戏
玩家
对象彼此根本不了解,这可能是最好的解决方案。但这是最复杂的代码,可能比这里的答案更复杂。在那之前,我可能会单独发布另一个答案

下面是几个比较简单的解决方案

内部类解决方案: 在这个解决方案中,内部类总是有一个对外部类的隐式引用。不需要传入
游戏
对象,因为它总是在
玩家
类的实例范围内

import javax.annotation.Nonnull;

public class Q23726363B
{
    public static void main(final String[] args)
    {
        final Game game = new Game(args[0], args[1]);
    }

    public static class Game
    {
        private final Player p1;
        private final Player p2;

        public Game(@Nonnull final String p1, @Nonnull final String p2)
        {
            this.p1 = new Player(p1);
            this.p2 = new Player(p2);
        }

        public class Player
        {
            private final String name;

            private Player(@Nonnull final String name) {this.name = name;}

            public Game getGame() { return Game.this; }
        }
    }
}
工厂方法解决方案: 使游戏对象成为玩家对象工厂。通过使这两个对象的构造函数都
私有
,您可以保证它们构造正确,并且通过不提供公开更改它们的方法,使引用在功能上不可变

使用
FactoryMethod
,如下所示:

import javax.annotation.Nonnull;

public class Q23726363A
{
    public static void main(final String[] args)
    {
        final Game game = Game.startGame(args[0], args[1]);
    }

    public static class Game
    {
        public static Game startGame(@Nonnull final String playerOneName, @Nonnull final String playerTwoName)
        {
            final Player p1 = new Player(playerOneName);
            final Player p2 = new Player(playerTwoName);
            final Game game = new Game(p1, p2);
            p1.setCurrentGame(game);
            p2.setCurrentGame(game);
            return game;
        }

        private final Player player1;
        private final Player player2;

        private Game(@Nonnull final Player player1, @Nonnull final Player player2)
        {
            this.player1 = player1;
            this.player2 = player2;
        }
    }

    public static class Player
    {
        private final String name;
        private Game currentGame;

        private Player(@Nonnull final String name)
        {
            this.name = name;
        }

        private void setCurrentGame(@Nonnull final Game currentGame)
        {
            this.currentGame = currentGame;
        }
    }
}
笔记: 您可能想在
游戏
构造函数中创建
玩家
对象,并将
传递到
玩家
对象构造函数中,以设置对
游戏
对象的引用

抵制这种诱惑,这称为泄漏构造函数中的
this
引用,这是不好的,因为它失去了
游戏
对象完全形成的所有保证

此外,这两种解决方案彼此之间仍然存在循环依赖关系


第一个并不坏,因为玩家类是游戏的一个内部类。第二个是一个简单但简单的解决方案,适用于小规模应用程序,但不适用于更复杂的大型应用程序。

Game
对象添加到
Player
对象不会“破坏游戏对象的不变性”它创建了一个循环引用,这是您真正应该关心的。我很高兴这显然是有用的!你最终得到了什么解决方案?到目前为止还没有,但我知道我将要做的事情会在将来引起麻烦,因此这个问题。。。我现在拥有的是一名球员,需要攻击“另一名球员”,因此我需要以某种方式抓住他。