C# OOP类设计问题:Monopoly Deal控制台游戏中如何实现Player类的决策逻辑与跨Player数据访问
如何以OOP方式从被包含类访问其他被包含类的数据?
你的问题非常典型——很多开发者在做游戏类OOP设计时,都会陷入「核心类臃肿」和「封装性冲突」的困境。先给你点个赞,你已经意识到了Game类职责过载的问题,这是走向良好OOP设计的第一步。
先分析你提出的几个方案的优缺点,再给你两个更合理的OOP思路:
对你现有方案的点评
- GameState有限视图:这个思路方向是对的,不用太担心内存冗余——你可以用只读接口+动态视图来实现,而不是复制整个数据,这样既保证Player只能拿到必要信息,又不会浪费内存。
- 传递整个Game引用:确实违反OOP设计原则,会导致Player和Game强耦合,Player甚至可能随意修改全局状态,彻底破坏封装性,不推荐。
- 保留现有设计:短期实现简单,但长期来看,Game类会变成「上帝类」,规则逻辑、AI决策、状态维护全部堆在一起,后续修改或扩展(比如加新卡牌、新规则)会非常痛苦。
- 重新设计类结构:这正是我们需要的,核心是职责分离——让每个类只做自己擅长的事。
推荐方案一:只读状态接口 + 职责拆分
把你的代码拆成三个核心角色,各司其职:
- Player:只负责「自身决策」(比如选偷谁的牌、出什么牌),持有自己的私有卡牌数据,通过只读接口获取外部必要信息。
- IReadOnlyGameState:定义Player能看到的所有全局信息(只读),比如其他玩家的公开状态、当前回合、动作合法性判断。
- GameManager:负责维护真实游戏状态、执行规则验证、处理Player的动作请求、更新全局状态。
示例代码(C#)
// 1. 定义只读接口,限制Player能访问的信息 public interface IReadOnlyPlayerState { Guid PlayerId { get; } int HandCardCount { get; } bool HasStealableCards { get; } } public interface IReadOnlyGameState { IReadOnlyList<IReadOnlyPlayerState> AllPlayers { get; } Guid CurrentTurnPlayerId { get; } bool IsActionValid(Guid playerId, GameAction action); } // 2. Player类:专注决策,依赖只读状态 public class Player { private readonly Guid _id; private readonly List<Card> _handCards = new(); public Player(Guid id) { _id = id; } // 决策方法:接收只读状态,返回要执行的动作 public GameAction ChooseAction(IReadOnlyGameState gameState) { // 根据只读状态判断:有没有可偷的玩家? var stealableTargets = gameState.AllPlayers .Where(p => p.HasStealableCards && p.PlayerId != _id) .ToList(); if (stealableTargets.Any()) { return new StealAction(_id, stealableTargets.First().PlayerId); } // 没有可偷的,就出第一张手牌 return new PlayCardAction(_id, _handCards.First()); } // 内部方法:只有GameManager能调用,修改自身状态 internal void AddCard(Card card) => _handCards.Add(card); internal Card RemoveStealableCard() => _handCards.First(c => c.IsStealable); internal bool HasStealableCards() => _handCards.Any(c => c.IsStealable); } // 3. GameManager:维护状态+执行规则,实现只读接口 public class GameManager : IReadOnlyGameState { private readonly List<Player> _players = new(); private Guid _currentTurnId; // 实现只读接口:返回玩家的只读视图 public IReadOnlyList<IReadOnlyPlayerState> AllPlayers => _players.Select(p => new ReadOnlyPlayerWrapper(p)).ToList(); public Guid CurrentTurnPlayerId => _currentTurnId; public void RunNextTurn() { var currentPlayer = _players.First(p => p.PlayerId == _currentTurnId); // 把只读状态传递给Player做决策 var chosenAction = currentPlayer.ChooseAction(this); // 验证动作合法性 if (!IsActionValid(chosenAction.PlayerId, chosenAction)) { throw new InvalidOperationException("该动作不符合规则!"); } // 执行动作 ExecuteAction(chosenAction); // 切换回合 _currentTurnId = GetNextPlayerId(); } public bool IsActionValid(Guid playerId, GameAction action) { // 这里放规则验证逻辑:比如偷牌时目标是否有可偷卡牌、当前玩家是否有偷牌道具等 if (action is StealAction steal) { var targetPlayer = _players.First(p => p.PlayerId == steal.TargetId); return targetPlayer.HasStealableCards(); } return true; } private void ExecuteAction(GameAction action) { if (action is StealAction steal) { var stealer = _players.First(p => p.PlayerId == steal.PlayerId); var target = _players.First(p => p.PlayerId == steal.TargetId); var stolenCard = target.RemoveStealableCard(); stealer.AddCard(stolenCard); } // 其他动作的执行逻辑... } // 辅助类:把Player包装成只读状态 private class ReadOnlyPlayerWrapper : IReadOnlyPlayerState { private readonly Player _player; public Guid PlayerId => _player.PlayerId; public int HandCardCount => _player.HandCardCount; public bool HasStealableCards => _player.HasStealableCards(); public ReadOnlyPlayerWrapper(Player player) => _player = player; } // 动作基类和具体动作 public abstract class GameAction { public Guid PlayerId { get; } protected GameAction(Guid playerId) => PlayerId = playerId; } public class StealAction : GameAction { public Guid TargetId { get; } public StealAction(Guid playerId, Guid targetId) : base(playerId) => TargetId = targetId; } public class PlayCardAction : GameAction { public Card Card { get; } public PlayCardAction(Guid playerId, Card card) : base(playerId) => Card = card; } }
这个方案的优点
- 职责清晰:Player管决策,GameManager管规则和状态,互不干涉。
- 封装性强:Player只能通过只读接口获取信息,无法修改全局状态,避免耦合。
- 可扩展性好:后续加AI玩家,只需要重写Player的
ChooseAction方法;加新规则,只修改GameManager的验证逻辑。
推荐方案二:中介者模式(适合复杂游戏)
如果你的游戏后续会有大量玩家间的交互(比如组队、交易),可以用中介者模式:让Game作为玩家之间的「中介」,Player之间不直接通信,所有交互都通过中介者完成。
核心思路:
- 定义
IGameMediator接口,提供Player需要的查询和交互方法。 - Player依赖这个接口,而不是具体的Game类,降低耦合。
- Game实现
IGameMediator,负责转发请求和验证规则。
简化示例
public interface IGameMediator { bool CanStealFrom(Player stealer, Player target); IEnumerable<Player> GetOtherPlayers(Player currentPlayer); } public class Player { private readonly Guid _id; private readonly List<Card> _handCards = new(); private readonly IGameMediator _mediator; public Player(Guid id, IGameMediator mediator) { _id = id; _mediator = mediator; } public GameAction ChooseAction() { // 通过中介者获取其他玩家信息 var otherPlayers = _mediator.GetOtherPlayers(this); var stealableTargets = otherPlayers.Where(p => _mediator.CanStealFrom(this, p)).ToList(); if (stealableTargets.Any()) { return new StealAction(_id, stealableTargets.First().PlayerId); } return new PlayCardAction(_id, _handCards.First()); } // 内部方法,仅中介者可调用 internal bool HasStealableCards() => _handCards.Any(c => c.IsStealable); } public class Game : IGameMediator { private readonly List<Player> _players = new(); public bool CanStealFrom(Player stealer, Player target) { // 规则验证逻辑 return target.HasStealableCards() && IsStealAllowedByRules(stealer); } public IEnumerable<Player> GetOtherPlayers(Player currentPlayer) { return _players.Where(p => p.PlayerId != currentPlayer.PlayerId); } // 其他游戏逻辑... }
这个方案的优点
- 符合迪米特法则(最少知识原则):Player只需要知道中介者,不需要知道其他Player的存在。
- 解耦Player之间的依赖:后续修改玩家交互逻辑,只需要修改中介者,不需要改动Player类。
最终建议
- 如果你的《Monopoly Deal》是中小型规模,方案一足够简单清晰,上手快,维护成本低。
- 如果后续要做复杂扩展(比如多人联机、自定义规则),可以考虑方案二加上规则引擎的拆分(把规则逻辑从Game中抽离到单独的
RuleEngine类)。
内容的提问来源于stack exchange,提问作者Lou
相关产品推荐
相关产品推荐

