← SEARCHABLE ARCHIVE
C# · 2026-08-12

从 C# 基础到掌握 — 第 10 集

将 C# 组织成可测试的游戏结构

INTERACTIVE READING MODE →

该博客上撰写的文章是出于教学目的而创建的。 另外,文章中的信息可能不正确,请谨慎。

了解大量语法和创建易于更改的程序之间是有区别的。 最后,让我们将游戏规则、保存和屏幕分开,以创建一个可以在小单元中验证的结构。

将规则放入纯对象中

public sealed class CombatService
{
    public int CalculateDamage(int attack, int defense)
    {
        return Math.Max(1, attack - defense);
    }

    public void Attack(Character attacker, Character target)
    {
        int damage = CalculateDamage(attacker.Attack, target.Defense);
        target.TakeDamage(damage);
    }
}

如果您的战斗规则不直接引用 UI 或引擎对象,您可以在控制台和测试中使用相同的代码。 屏幕只负责显示结果和传输输入。

外部函数放在接口后面

public interface ISaveStore
{
    Task SaveAsync(SaveData data, CancellationToken token);
    Task<SaveData?> LoadAsync(CancellationToken token);
}

public sealed class GameSession
{
    private readonly ISaveStore saveStore;

    public GameSession(ISaveStore saveStore)
    {
        this.saveStore = saveStore;
    }
}

通过将文件或服务器实现放在接口后面,您可以放置​​一个假实现来进行测试,而不是真正的存储库。 这不是隐藏功能的层,而是可能以不同方式更改的分隔代码的边界。

测试运行

[Fact]
public void DamageNeverDropsBelowOne()
{
    var combat = new CombatService();

    int damage = combat.CalculateDamage(5, 100);

    Assert.Equal(1, damage);
}

该测试检查外部可见的规则,而不是简单地遵循私有方法的实现。 然后,您可以看到即使组织了内部结构,是否仍保持相同的操作。

设置引擎边界

在Unity中,一个MonoBehaviour可以接收输入和生命周期,而一个常规的C#对象可以分为负责规则。 将所有类设为 MonoBehaviour 可以更轻松地连接到编辑器,但创建和测试与引擎相关。

一个好的结构并不是拥有大量的文件。 状态所有者、异步操作的生命周期、事件取消订阅和外部资源边界在代码中结构清晰。

C# 的类型、接口、泛型、事件、LINQ 和异步不是单独的技术。 它是一种共同使用的工具,用于划分可变部分并减少不正确的状态。