Java海战开发:如何从持有子对象引用的类访问父对象
我之前开发格子类策略游戏时也遇到过类似的需求,针对你这个海战游戏的场景,有几个实用的解决方案,按推荐程度排序:
方案1:给舰段(ShipSegment)添加父战舰引用(最推荐)
这是最直观也最符合面向对象设计的方式——既然每个舰段都属于某一艘战舰,那就在ShipSegment类里直接持有所属Ship的引用,在创建舰段时就把父战舰传进去初始化。这样单元格拿到ShipSegment后,直接调用 getter 就能获取到对应的战舰。
示例代码:
public class ShipSegment { // 用final确保引用不会被意外修改 private final Ship parentShip; private int x; private int y; // 构造方法传入父战舰 public ShipSegment(Ship parentShip, int x, int y) { this.parentShip = parentShip; this.x = x; this.y = y; } // 提供getter方法 public Ship getParentShip() { return parentShip; } // 你的place()方法 public void place(int newX, int newY) { this.x = newX; this.y = newY; // 其他逻辑... } }
在创建战舰和舰段时,只需要这样关联:
// 创建一艘战舰 Ship destroyer = new Ship(); // 创建舰段时传入所属战舰 ShipSegment seg1 = new ShipSegment(destroyer, 2, 3); ShipSegment seg2 = new ShipSegment(destroyer, 2, 4); // 将舰段添加到战舰的管理列表(如果需要的话) destroyer.addSegment(seg1); destroyer.addSegment(seg2);
之后单元格要获取战舰时,直接通过舰段引用调用:
// 假设你的Cell类有getShipSegment()方法 ShipSegment segment = cell.getShipSegment(); if (segment != null) { Ship parentShip = segment.getParentShip(); // 接下来处理父战舰的逻辑... }
这个方案的优点是逻辑清晰、访问效率高,而且舰段和战舰的关联关系是显式的,后期维护起来也方便。
方案2:使用映射表(Map)关联舰段与战舰
如果出于某些原因(比如不想让ShipSegment直接依赖Ship),可以用一个Map<ShipSegment, Ship>来存储舰段到战舰的映射关系。这个映射可以放在游戏的核心管理类里,比如SeaBattleGame。
示例代码:
public class SeaBattleGame { // 维护舰段到战舰的映射 private Map<ShipSegment, Ship> segmentToShipMap = new HashMap<>(); // 创建战舰的方法 public Ship createShip(List<Point> segmentCoords) { Ship ship = new Ship(); for (Point coord : segmentCoords) { ShipSegment segment = new ShipSegment(coord.x, coord.y); ship.addSegment(segment); // 将舰段和战舰关联存入映射 segmentToShipMap.put(segment, ship); } return ship; } // 通过舰段获取战舰的方法 public Ship getShipBySegment(ShipSegment segment) { return segmentToShipMap.get(segment); } }
然后单元格获取战舰时:
ShipSegment segment = cell.getShipSegment(); if (segment != null) { Ship parentShip = game.getShipBySegment(segment); // 处理逻辑... }
这个方案的好处是解耦了ShipSegment和Ship,但需要注意映射的生命周期管理:当战舰被销毁(比如被击沉)时,要记得从映射中移除对应的所有舰段条目,避免内存泄漏。
方案3:遍历战舰列表查找(不推荐)
如果以上两种方案都不适用,还可以通过遍历所有战舰的舰段列表来找到对应的战舰,但这个方法的效率很低,尤其是当游戏中的战舰数量较多时,不建议使用。
示例代码:
public class Ship { // 维护所有战舰的全局列表(或者放在游戏管理类里) private static List<Ship> allShips = new ArrayList<>(); private List<ShipSegment> segments = new ArrayList<>(); public Ship() { allShips.add(this); } // 静态方法:通过舰段查找所属战舰 public static Ship findShipForSegment(ShipSegment target) { for (Ship ship : allShips) { if (ship.segments.contains(target)) { return ship; } } return null; } }
使用时:
ShipSegment segment = cell.getShipSegment(); if (segment != null) { Ship parentShip = Ship.findShipForSegment(segment); // 处理逻辑... }
这个方案的缺点很明显:每次查找都要遍历所有战舰和它们的舰段,性能差,而且全局列表的管理也容易出问题(比如战舰销毁时忘记从列表移除)。
总结
优先选择方案1,它最符合面向对象的设计原则,代码简洁且易于维护。如果需要解耦,可以考虑方案2,但要做好映射的生命周期管理。方案3只适合极小规模的游戏场景,尽量避免使用。
内容的提问来源于stack exchange,提问作者Axel Köhler

