今天我们来进行学习继承

游戏中,哥布林和史莱姆都是敌人,它们有共同的属性(血量、攻击力),又有各自独特的行为。这时就轮到继承出场了。

我们先来了解两个简单的概念,子类和父类

1. 抽出敌人的父类

// 这个类定义了所有敌人的“公共模板”
class Enemy
{
    public string name;
    public int hp;
    public int attackPower;

    // 基类构造函数
    public Enemy(string enemyName, int enemyHp, int enemyAttack)
    {
        name = enemyName;
        hp = enemyHp;
        attackPower = enemyAttack;
    }

    // 通用受伤逻辑
    public void TakeDamage(int damage)
    {
        hp = hp - damage;
        Console.WriteLine(name + “ 受到了” + damage + “点伤害!剩余HP:” + hp);
        if (hp <= 0)
        {
            // 后面讲多态时会让它更有用
            Console.WriteLine(name + “ 被消灭了!”);
        }
    }

    // 通用攻击,用 virtual 表示子类可以重写它
    public virtual void Attack()
    {
        Console.WriteLine(name + “ 发动了攻击,造成” + attackPower + “点伤害!”);
    }
}

2. 创建子类,继承敌人

子类的语法是 class 子类名 : 父类名

// 哥布林继承了 Enemy 的一切
class Goblin : Enemy
{
    // 哥布林独有字段
    public int stealAmount;

    // 子类构造函数,需要用 base() 关键字调用父类的构造函数
    public Goblin(string name, int hp, int attack, int steal) : base(name, hp, attack)
    {
        stealAmount = steal;
    }

    // 哥布林特有技能
    public void Steal()
    {
        Console.WriteLine(name + “ 偷走了你” + stealAmount + “个金币!”);
    }

    // 重写父类的 Attack 方法
    public override void Attack()
    {
        Console.WriteLine(name + “ 用匕首刺击,造成” + attackPower + “点伤害!”);
    }
}

现在来说一下多态

现在,Goblin 同时拥有从 Enemy 继承来的 TakeDamage 和自己独有的 Steal 方法。

现在最关键的一步来了。如果游戏里有10种敌人,我不想针对每种敌人都写一段攻击逻辑。我想写一个通用的 ProcessBattle 方法,不管传进来的是哥布林还是巨龙,都能正确地执行它们自己的攻击。

多态 的意思就是:一个父类的引用,可以指向任意一个子类的对象,并且调用方法时,会自动执行那个子类特有的版本。

在 Main 里写这个通用方法:

// 这个方法是多态的精髓
static void ProcessBattle(Enemy someEnemy)
{
    Console.WriteLine(“\n--- 遭遇敌人:” + someEnemy.name + “ ---”);
    someEnemy.Attack(); // 它调用的是谁的Attack?取决于传进来的是什么!
    someEnemy.TakeDamage(30);
}

然后在 Main 里实验:

static void Main(string[] args)
{
    Goblin bob = new Goblin(“哥布林小偷”, 50, 12, 10);
    Slime slime = new Slime(“绿色史莱姆”, 35, 6);

    // 核心:Goblin 和 Slime 都是 Enemy,所以都能作为参数传入
    ProcessBattle(bob);
    ProcessBattle(slime);
    // 观察输出,“哥布林”会用匕首刺击,“史莱姆”会跳起来砸你。
    // 同样的 someEnemy.Attack() 代码,却产生了完全不同的行为。
}

ProcessBattle 的参数类型是 Enemy,它能接受任何 Enemy 的子类。当调用 someEnemy.Attack() 时,程序会自动查你传进来的真实对象是哥布林还是史莱姆,然后执行它们各自重写后的 Attack 方法。这就是多态。

最后是封装

保护数据——封装

目前所有字段都是 public,外部可以随意修改。这很危险:别人写 slime.hp = -9999; 怎么办?

封装 就是用 private 把数据藏起来,再提供安全的公开入口(通常用属性)进ji'che行访问。

改造 Enemy 基类的 hp 字段:

class Enemy
{
    // 字段设为私有,外部无法直接访问
    private int hp;
    
    // 用属性 (Property) 来提供安全读写
    public int Hp
    {
        get { return hp; }  // 读的时候,直接返回私有hp
        set 
        { 
            // 写的时候,加上规则!血量不能小于0
            if (value < 0) 
            {
                hp = 0;
                Console.WriteLine(name + “的血量已归零。”);
            }
            else
            {
                hp = value;
            }
        }
    }

    public string Name { get; private set; } // 公共读,私有写,名字只有自己能设

    // 构造函数里,现在要设置属性,而不是字段
    public Enemy(string name, int hp, int attack)
    {
        Name = name;
        Hp = hp; // 这里会触发属性的 set 逻辑
        AttackPower = attack;
    }

    // ... 其他代码,使用 Hp 和 Name 替代原来的字段 ...
}

现在你从外部访问 enemy1.Hp = -100;,血量会被安全地设为0。

今天提到的知识点比较多,大家要认真学习,简单来说,继承就是设置一个怪物模板,把其它怪物直接套用上去,不用一个一个设置血量,攻击等模板。多态是筛选和分流,封装是保护代码不会轻易的报错,提供一个安全的阈值。

今天的学习内容就此结束,关注我,下一期更精彩

更多推荐