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

当持有基类Player对象但需访问派生类专属Stats字段时,应采用何种设计模式?

优雅解决玩家行为与专属统计字段的访问问题

首先,先明确你的核心需求:既要通过统一的基类调用doThing(),又要在特定行为类中安全访问MeleeStats/RangedStats的专属字段,同时保留Player和Stats体系的分层设计。你提到的几种方案各有缺陷,这里推荐两个更优的设计思路:


方案一:泛型抽象类 + 类型约束(推荐,简洁且类型安全)

这个方案通过泛型给行为类添加类型约束,既保留基类的通用性,又能在派生类中直接访问特定类型的Player和Stats,完全避免强制转换。

先看修改后的代码实现:

// 保留你原有的Player与Stats层级结构,仅做少量可读性优化
abstract class Player { 
    public abstract Stats GetStats(); 
}

abstract class RangedPlayer : Player { 
    public RangedStats RangedStats { get; init; }
    public override Stats GetStats() => RangedStats; 
}

abstract class MeleePlayer : Player { 
    public MeleeStats MeleeStats { get; init; }
    public override Stats GetStats() => MeleeStats; 
}

class Stats { 
    public int MaxHealth { get; set; }
}

class RangedStats : Stats { 
    public int MaxAmmo { get; set; }
}

class MeleeStats : Stats { 
    public int MeleeComboLength { get; set; }
}

// 非泛型基类:用于统一调用doThing(),满足你"无需关注具体类型"的需求
abstract class ThingPlayersCanDo {
    protected Player BasePlayer { get; }
    
    protected ThingPlayersCanDo(Player player) {
        BasePlayer = player;
    }

    public abstract void DoThing();
}

// 泛型抽象类:添加类型约束,让派生类可以直接绑定到特定Player/Stats类型
abstract class ThingPlayersCanDo<TPlayer, TStats> : ThingPlayersCanDo 
    where TPlayer : Player 
    where TStats : Stats {
    protected TPlayer Player { get; }
    protected TStats PlayerStats { get; }

    protected ThingPlayersCanDo(TPlayer player) : base(player) {
        Player = player;
        // 这里的转换是安全的:因为TPlayer的GetStats必然返回对应TStats类型
        PlayerStats = (TStats)player.GetStats();
    }
}

// 近战专属行为类:直接使用MeleePlayer和MeleeStats,无需转换
class ThingMeleePlayersCanDo : ThingPlayersCanDo<MeleePlayer, MeleeStats> {
    public ThingMeleePlayersCanDo(MeleePlayer player) : base(player) { }

    public override void DoThing() {
        // 访问通用属性
        Console.WriteLine($"当前生命值上限:{PlayerStats.MaxHealth}");
        // 直接访问近战专属字段
        Console.WriteLine($"可触发连击次数:{PlayerStats.MeleeComboLength}");
    }
}

// 远程专属行为类:同理
class ThingRangedPlayersCanDo : ThingPlayersCanDo<RangedPlayer, RangedStats> {
    public ThingRangedPlayersCanDo(RangedPlayer player) : base(player) { }

    public override void DoThing() {
        Console.WriteLine($"当前生命值上限:{PlayerStats.MaxHealth}");
        Console.WriteLine($"当前弹药上限:{PlayerStats.MaxAmmo}");
    }
}

这个方案的优势:

  • 类型安全:编译时就能校验Player与Stats的类型匹配,完全避免运行时转换错误;
  • 通用性保留:非泛型的ThingPlayersCanDo基类允许你在不关心具体玩家类型的场景下,统一调用DoThing();
  • 扩展性强:后续新增MagicPlayer这类角色时,只需要新增对应的ThingMagicPlayersCanDo继承泛型基类即可;
  • 代码简洁:无需冗余的字段合并或字典存储,保持原有的类分层设计。

方案二:访问者模式(适合频繁新增行为的场景)

如果你的游戏后续需要给玩家体系添加大量不同的行为,且不想频繁修改Player类本身,访问者模式会更符合开闭原则(对扩展开放,对修改关闭)。

实现思路是让Player类接受一个访问者,由访问者来处理不同类型玩家的专属逻辑:

// 定义访问者接口,每个Player类型对应一个Visit方法
interface IPlayerActionVisitor {
    void Visit(MeleePlayer player);
    void Visit(RangedPlayer player);
}

// 修改Player基类,添加Accept方法
abstract class Player { 
    public abstract Stats GetStats(); 
    public abstract void Accept(IPlayerActionVisitor visitor);
}

class RangedPlayer : Player { 
    public RangedStats RangedStats { get; init; }
    public override Stats GetStats() => RangedStats; 
    public override void Accept(IPlayerActionVisitor visitor) => visitor.Visit(this);
}

class MeleePlayer : Player { 
    public MeleeStats MeleeStats { get; init; }
    public override Stats GetStats() => MeleeStats; 
    public override void Accept(IPlayerActionVisitor visitor) => visitor.Visit(this);
}

// 把"玩家能做的事"实现为访问者
class MeleeComboAction : IPlayerActionVisitor {
    public void Visit(MeleePlayer player) {
        // 直接访问近战玩家的专属统计字段
        Console.WriteLine($"触发连击,最大连击数:{player.MeleeStats.MeleeComboLength}");
    }

    public void Visit(RangedPlayer player) {
        // 对不支持的玩家类型,可以抛出异常或做兼容处理
        throw new InvalidOperationException("远程玩家无法触发近战连击");
    }
}

// 使用方式
var meleePlayer = new MeleePlayer { MeleeStats = new MeleeStats { MaxHealth = 120, MeleeComboLength = 4 } };
var comboAction = new MeleeComboAction();
meleePlayer.Accept(comboAction);

这个方案的优势:

  • 行为与玩家解耦:新增行为时不需要修改Player类,只需要新增访问者实现;
  • 集中处理逻辑:同类型的行为逻辑可以集中在一个访问者类中,便于维护;

缺点:

  • Player类型扩展成本高:如果新增MagicPlayer,需要修改访问者接口和所有现有访问者实现,违反开闭原则,所以适合Player类型相对固定的场景。

方案对比

  • 如果你只是需要解决当前的类型访问问题,且Player类型不会频繁新增,泛型方案是更简洁直接的选择;
  • 如果你需要给玩家体系添加大量不同的行为,且Player类型相对稳定,访问者模式会更适合。

内容的提问来源于stack exchange,提问作者Houou In Kyouma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 14:17:35