第 7 課 · 約 14 分鐘 · ← 第 6 課

繼承與介面

型別之間有兩種關係:繼承("狗是一種動物——繼承它的一切")和介面("機器人會 Speak()——承諾一種能力")。兩者存在的目的都是讓一份程式碼通過同一個形狀操作許多型別。

virtual / override:基類允許你替換行為

class Animal
{
    public string Name;

    public Animal(string name)
    {
        Name = name;
    }

    public virtual void Speak()          // virtual:子類可以替換
    {
        Console.WriteLine(Name + " makes a sound");
    }
}

class Dog : Animal                        // Dog 繼承 Animal
{
    public Dog(string name) : base(name) { }   // base(...):先跑 Animal 的建構子

    public override void Speak()          // 替換它
    {
        Console.WriteLine(Name + " says woof");
    }
}

Animal a = new Dog("Rex");     // 狗是一種動物——這個賦值合法
a.Speak();                     // 分發跟著物件走:Dog 的版本執行
Animal b = new Animal("Generic");
b.Speak();

最後兩行是全部要點:變數型別是 Animal,但執行的方法是實際物件上的那個——虛分發。一個遍歷混合陣列的迴圈,挨個調 Speak(),每次都叫得恰到好處。

base:在父類的基礎上蓋樓

base(...) 調父類建構子;base.Method() 調父類版本——標準的"擴充套件而非替換"形狀:

class Animal
{
    public virtual void Speak() { Console.WriteLine("..."); }
}

class Cat : Animal
{
    public override void Speak()
    {
        base.Speak();                  // 先做動物都會做的……
        Console.WriteLine("meow");     // ……再加貓的
    }
}

is:"它真的是那個嗎?"——順便繫結變數

class Animal { }
class Dog : Animal
{
    public void Fetch()
    {
        Console.WriteLine("fetching!");
    }
}

Animal x = new Dog();          // 向上轉:沒問題,Dog 是 Animal

if (x is Dog d)                // 測試 + 宣告一步到位
{
    d.Fetch();                 // 只有狗會撿東西
}

安靜的兄弟 as 負責轉換、失敗時給 null:Dog dd = x as Dog;——然後檢查 dd != null。只用來分支用 is;轉換後還要用值用 as。

介面:是承諾,不是血緣

interface IGreeter
{
    void Greet();               // 一紙契約:沒有方法體,只有承諾
}

class EnglishGreeter : IGreeter
{
    public void Greet() { Console.WriteLine("Hello"); }
}

class SpanishGreeter : IGreeter
{
    public void Greet() { Console.WriteLine("Hola"); }
}

IGreeter[] greeters = { new EnglishGreeter(), new SpanishGreeter() };   // 互不相干的類,同一個承諾
foreach (IGreeter g in greeters)
{
    g.Greet();                 // 各自履行各自的行為
}

兩個問候者沒有共同基類——介面不是 ancestry,是共同的承諾。這就是為什麼 C# 允許一個類實現很多介面、卻只能繼承一個基類:血緣排他,承諾不排他。

哪個工具,什麼時候?

場景用
"X 是一種 Y",共享程式碼繼承(class Dog : Animal)
"X 能做 Z",橫跨不同家族介面(: IComparable)
子類微調父類行為virtual + override
子類擴充套件父類行為override + base.Method()
安全向下轉後再用額外成員is(分支)/ as(保留)

現在就練