You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在卡牌游戏设计中遵循接口隔离原则且避免类型转换?

卡牌游戏设计:遵循接口隔离原则并避免类型转换

问题背景

我正在开发一个个人项目的简单卡牌游戏,规则如下:

  • 卡牌分为action、item、champion三种类型
  • 玩家可将卡牌置于自己的playing field
  • 打出action或item卡牌会立即触发其效果
  • champion卡牌进入playing field时处于untapped状态,需被tapped才能激活能力
  • 回合结束时,action和item卡牌会被弃置,champion卡牌则留在场上并恢复为untapped状态
  • 玩家可直接攻击对手,或攻击对手场上的champion卡牌

最初的简化设计代码:

interface Card {}

class AbstractCard implements Card {}

class ActionCard extends AbstractCard {}

class ItemCard extends AbstractCard {}

class ChampionCard extends AbstractCard {}

class PlayingField {
  public Collection<Card> getPlayedCards() {}
}

class Player {
  private final PlayingField playingField;

  public Collection<Card> getPlayedCards() {
    return playingField.getPlayedCards();
  }
}

在实现攻击规则时,若在Card接口中添加takeDamage()方法,ItemCard和ActionCard需实现该方法但永远不会被调用,只能抛出NotTargetableException,违反了接口隔离原则(Interface Segregation Principle)。

调整后的设计:

interface Attackable {
  void takeDamage(Combat combat);
}

class ChampionCard extends AbstractCard implements Attackable {}

class Player implements Attackable {}

但此时getPlayedCards()返回Card集合,攻击时需要强制转换为Attackable,这是代码坏味道。

我的问题:如何在这种场景下遵循接口隔离原则且避免类型转换?

补充:我想到一个方案——在PlayingField中新增targetableCards()方法返回Attackable类型集合,将可被攻击的卡牌加入该集合,这是否合规?


解决方案分析

你提出的targetableCards()方案是合规且推荐的

这个方案完全契合接口隔离原则,同时从根源上消除了类型转换的需求:

  1. 职责分离清晰:PlayingField作为场上卡牌的管理者,分别提供getPlayedCards()(返回所有场上卡牌)和targetableCards()(仅返回可被攻击的目标)两个方法,调用方无需自行判断类型,直接按需调用即可。
  2. 封装逻辑合理:哪些卡牌可被攻击的判断逻辑,放在PlayingField内部比外部调用方处理更合适,符合单一职责原则,也避免了重复的类型检查代码。

进一步优化建议

  1. 细化PlayingField的内部卡牌管理:
    在PlayingField内部拆分维护不同类型的卡牌集合,比如actionCards、itemCards、championCards,这样:

    • getPlayedCards()可以通过合并这些集合返回所有场上卡牌
    • targetableCards()直接返回championCards(因为只有它实现了Attackable)
      内部管理更清晰,也避免了添加卡牌时的重复判断。
  2. 统一攻击目标的获取逻辑:
    玩家本身也是Attackable,可以在Player类中新增getAllAttackableTargets()方法,返回包含自身和场上targetableCards()的集合,让攻击方可以一次性获取所有可攻击目标(玩家+对方场上的冠军卡牌)。

  3. 增强集合的安全性:
    返回targetableCards()时,可以返回不可修改的集合视图(比如Collections.unmodifiableList()),避免外部代码直接修改场上的可攻击卡牌集合。


示例优化代码

interface Card {}

abstract class AbstractCard implements Card {}

class ActionCard extends AbstractCard {}

class ItemCard extends AbstractCard {}

interface Attackable {
  void takeDamage(Combat combat);
}

class ChampionCard extends AbstractCard implements Attackable {
  @Override
  public void takeDamage(Combat combat) {
    // 实现冠军卡牌受伤害逻辑
  }
}

class PlayingField {
  private final Collection<ActionCard> actionCards = new ArrayList<>();
  private final Collection<ItemCard> itemCards = new ArrayList<>();
  private final Collection<ChampionCard> championCards = new ArrayList<>();

  public Collection<Card> getPlayedCards() {
    return Stream.concat(
        Stream.concat(actionCards.stream(), itemCards.stream()),
        championCards.stream()
    ).collect(Collectors.toUnmodifiableList());
  }

  public Collection<Attackable> getTargetableCards() {
    return Collections.unmodifiableCollection(championCards);
  }

  public void addCard(Card card) {
    if (card instanceof ActionCard) {
      actionCards.add((ActionCard) card);
    } else if (card instanceof ItemCard) {
      itemCards.add((ItemCard) card);
    } else if (card instanceof ChampionCard) {
      championCards.add((ChampionCard) card);
    }
  }

  public void endTurn() {
    actionCards.clear();
    itemCards.clear();
    championCards.forEach(champion -> {
      // 恢复为untapped状态的逻辑
    });
  }
}

class Player implements Attackable {
  private final PlayingField playingField;

  public Player(PlayingField playingField) {
    this.playingField = playingField;
  }

  @Override
  public void takeDamage(Combat combat) {
    // 玩家受伤害逻辑
  }

  public Collection<Attackable> getAllAttackableTargets() {
    Collection<Attackable> targets = new ArrayList<>(playingField.getTargetableCards());
    targets.add(this);
    return Collections.unmodifiableCollection(targets);
  }
}

内容的提问来源于stack exchange,提问作者Stefan Rendevski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 19:54:29