純虛擬函式(二)


在定義類別時,可以完全只有純虛擬函式,完全不提供實作,也沒有任何狀態定義,將類別當成是一種行為規範。

來個簡單的需求演變情境,以說明為什麼要有這種類別。老闆今天想開發一個海洋樂園遊戲,當中所有東西都會游泳。你想了一下,談到會游的東西,第一個想到的就是魚,你也許會定義 Fish 類別有個 swim 的行為:

class Fish {
protected:
    string name;

public:
    Fish(string name) : name(name) {}

    string getName() {
        return this->name;
    }

    virtual void swim() = 0;
};

由於實際上每種魚游泳方式不同,所以將 swim 定義為純虛擬函式,因此 Fish 是抽象類別。接著定義小丑魚繼承各種魚:

class Fish : public Swimmer {
protected:
    string name;

public:
    Fish(string name) : name(name) {}

    string getName() {
        return this->name;
    }
};

class Anemonefish : public Fish {
public:
    using Fish::Fish;

    void swim() override {
        cout << "小丑魚 " + this->name + " 游泳" << endl; 
    }
};

class Shark : public Fish {
public:
    using Fish::Fish;

    void swim() override {
        cout << "鯊魚 " + this->name + " 游泳" << endl; 
    }
};

class Piranha : public Fish {
public:
    using Fish::Fish;

    void swim() override {
        cout << "食人魚 " + this->name + " 游泳" << endl; 
    }
};

老闆說話了,為什麼都是魚?人也會游泳啊!怎麼沒寫?於是你就再定義 Human 類別繼承 Fish…等一下!Human 繼承 Fish? 不會覺得很奇怪嗎?人是一種魚嗎?既然如此,不如將 Fish 改名為 Swimmer,讓 Human 繼承 Swimmer,這樣好像說得過去,魚是一種會游泳的生物,人也是一種會游泳的生物嘛!

不過,如果 Human 要有 firstNamelastName 兩個值域呢?也就是說就算你將 Fish 改名為 Swimmer,原本的狀態定義,並不適合 Human 來繼承,怎麼辦呢?

既然都想要抽象的 swim,而狀態不同是個問題,不如就定義個沒有狀態的 Swimmer 吧!

class Swimmer {
public:
    virtual void swim() = 0;
    virtual ~Swimmer() = default;
};

然後原本的 Fish 繼承 Swimmer

class Fish : public Swimmer {
protected:
    string name;

public:
    Fish(string name) : name(name) {}

    string getName() {
        return this->name;
    }
};

Fish 方才的子類不用修改,也就是維持既有的繼承體系,接著 Human 也可以繼承 Swimmer

class Human : public Swimmer {
protected:
    string firstName;
    string lastName;

public:
    Human(string firstName, string lastName) : 
        firstName(firstName), lastName(lastName) {}

    string getFirstName() {
        return this->firstName;
    }

    string getLastName() {
        return this->lastName;
    }

    void swim() override {
        cout << "人類 " + this->firstName + " " + this->lastName + " 游泳" << endl; 
    }
};

那來個潛水艇吧!

class Submarine : public Swimmer {
protected:
    string nickName;

public:
    Submarine(string nickName) : nickName(nickName) {}

    string getNickName() {
        return this->nickName;
    }

    void swim() override {
        cout << "潛水艇 " + this->nickName + " 潛行" << endl; 
    }
};

現在,大家可以快快樂樂地一起游泳了:

...略

void doSwim(Swimmer &swimmer) {
    swimmer.swim();
}

int main() { 
    Anemonefish anemonefish("尼莫");
    Shark shark("蘭尼");
    Human human("賈斯汀", "林");
    Submarine submarine("黃色一號");

    doSwim(anemonefish);
    doSwim(shark);
    doSwim(human);
    doSwim(submarine);

    return 0;
}

執行結果如下:

小丑魚 尼莫 游泳
鯊魚 蘭尼 游泳
人類 賈斯汀 游泳
潛水艇 黃色一號 潛行
小丑魚 尼莫 游泳
鯊魚 蘭尼 游泳
人類 賈斯汀 林 游泳
潛水艇 黃色一號 潛行