今天補充昨天一些未交代細節。
昨天講到,實作多型時,通常是宣告父類別的型別,但實際new出的是子類別物件,例如Tree hardwood = new Hardwood();
。其中Tree
是「形式類別」(下改稱「形式型別」)(註2),亦即父類別;Hardwood
為「實際類別」(下稱「實際型別」),也就是子類別。
形式型別和實際型別不同,這樣的設計究竟有甚麼好處?「形實一致」可能帶來哪些不便?
其實昨天已部分回答了這個問題。現在進一步說明:
Tree hardwood = new Hardwood();
,hardwood物件就只能執行Tree
類別已宣告的方法,通常子類別都會覆寫override這些方法。而Tree
沒有宣告的方法,hardwood一律不能呼叫,一呼叫就報錯。另外一個好處是:這樣設計系統比較容易擴充。
假設下列情形:
TreeDoctor
類別。這個樹醫生類別醫術非常高明,甚麼樣的樹生病他都能治。不管是闊葉樹、針葉樹,甚至金星樹、火星樹、土星樹都難不倒這位神醫。一言以蔽之,只要是「樹」(Tree
),他就能治病。cure()
方法,接受一個Tree
型別的參數。所以這個方法的signature是public void cure(Tree tree)
(註3)。新增「樹醫生類別」的程式如下。為求精簡,刪去一些不相干的code:
using System;
namespace MyApplication
{
class Tree // Base class (parent)
{
}
class Hardwood : Tree // Derived class (child)
{
}
class Conifer : Tree // Derived class (child)
{
}
class VenusianTree : Tree // 金星樹
{
}
class MartianTree : Tree // 火星樹
{
}
class SaturnianTree : Tree // 土星樹
{
}
class TreeDoctor // 新增這個「樹醫生」類別。
{
public void cure(Tree tree) // 只要是「樹」我就能治。
{
Console.WriteLine("I am curing the " + tree + '.');
}
}
class Program
{
static void Main(string[] args)
{
Tree hardwood = new Hardwood(); // 「形式型別」是Tree,「實際型別」是Hardwood。
Tree conifer = new Conifer(); // 「形式型別」是Tree,「實際型別」是Conifer。
Tree venusianTree = new VenusianTree(); // 「形式型別」是Tree,「實際型別」是VenusianTree。
Tree martianTree = new MartianTree(); // 「形式型別」是Tree,「實際型別」是MartianTree。
Tree saturnianTree = new SaturnianTree(); // 「形式型別」是Tree,「實際型別」是SaturnianTree。
TreeDoctor doctor = new TreeDoctor();
// 樹醫生要去醫治不同的樹了:
doctor.cure(hardwood);
doctor.cure(conifer);
doctor.cure(venusianTree);
doctor.cure(martianTree);
doctor.cure(saturnianTree);
}
}
}
程式正確執行,樹醫生坐太空船到各星球出診去了:
但是...如果樹的物件「形式型別」和「實際型別」設為一致:
using System;
namespace MyApplication
{
class Tree // Base class (parent)
{
}
class Hardwood : Tree // Derived class (child)
{
}
class Conifer : Tree // Derived class (child)
{
}
class VenusianTree : Tree // 金星樹
{
}
class MartianTree : Tree // 火星樹
{
}
class SaturnianTree : Tree // 土星樹
{
}
class TreeDoctor // // 新增這個「樹醫生」類別。
{
public void cure(Tree tree) // 我要的參數是「通用型的樹」,而不是某個特定樹種。
{
Console.WriteLine("I am curing the " + tree + '.');
}
}
class Program
{
static void Main(string[] args)
{
hardwood hardwood = new Hardwood(); // 「形式型別」和「實際型別」都是Hardwood。
Conifer conifer = new Conifer(); // 「形式型別」和「實際型別」都是Conifer。
VenusianTree venusianTreeTree = new VenusianTree(); // 「形式型別」和「實際型別」都是VenusianTree。
MartianTree martianTree = new MartianTree(); // 「形式型別」和「實際型別」都是MartianTree。
SaturnianTree saturnianTree = new SaturnianTree(); // 「形式型別」和「實際型別」都是SaturnianTree。
TreeDoctor doctor = new TreeDoctor();
// 樹醫生要去醫治不同的樹了。但,可以嗎?
doctor.cure(hardwood);
doctor.cure(conifer);
doctor.cure(venusianTree);
doctor.cure(martianTree);
doctor.cure(saturnianTree);
}
}
}
樹醫生的cure()
方法,參數的型別必須為「通用的樹」,而不是某個「特定樹種」,主程式卻傳了一棵特定樹種型別的樹給他。這樣會引發錯誤:
這是Tree hardwood = new Hardwood();
寫法好處之一。當然還有其他優點,就恕不一一枚舉了。
連續兩天用C#講多型,似乎有點偏離本系列題目OO Programming in Python
。好,明天就介紹 Python的多型吧。
註1: 之前筆者也有提及,看過有人用些奇奇怪怪的方法去實作出Python的method overloading。基於實用性不大,過程又有點複雜,除非有心「練功」,否則不看也罷。
註2: 請參考本系列Day 2: 類別是「模板」或「藍圖」
。該文有說明類別就是自訂的型別。
註3: signature一般不包括傳回值。這裡為了完整表達整個方法的定義,特地將其傳回值void
也納入。
註4: 這些限制是筆者從Java的資料找到,不確定是否為通例。