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