【公告】網站目前停止所有的課程訂閱服務,原有學員權益不受影響,造成不便還請見諒,我們正在打造更多課程以及圖書,包含 Python 為主的課程主題,未來將會合併且擴充目前的課程內容,提供全新課程訂閱服務,感謝學員的支持。

繼承架構下的方法成員覆寫(1)

實際的應用開發中,衍生類別除了直接繼承來自於基礎類別的方法成員,通常也會根據自己的需求,作進一步的調整,包含擴充或是改寫,這種行為稱之為方法覆寫(Override)。
基礎類別的方法成員必需在設計時宣告為 virtual ,以支援衍生子類別的覆寫:

virtual T VMethod()
{
// …

virtual 關鍵字表示允許子類別改寫專屬自己版本的方法成員,而在子類別中覆寫基礎類別的 virtual 方法則需使用關鍵字 override 宣告方法成員,如下式:

override T VMethod()
{
// …

一旦子類別覆寫了基礎類別的方法,子類別所引用的將是覆寫的版本,而依據繼承的原則,覆寫行為並不會向上影響原來的基礎類別。
假設以下的 PReport 類別,支援報表列印功能的需求:

// 報表列印類別
class PReport
{
    public PReport()
    {
        Console.WriteLine("報表列印程式啟動 ....");
    }
    public virtual void PrintStart()
    {
        Console.WriteLine("報表開始列印...!!");
        Console.WriteLine("列印標準尺寸報表...!!");
        Console.WriteLine("工作完成...!!");
        FinishedWork("標準");
    }
    protected void FinishedWork(string reportType)
    {
        Console.WriteLine("{0} 報表列印完成...!!", reportType);
        Console.WriteLine("清除佇列工作...!!");
    }
}

PReport 作為基礎類別,其中定義了 virtual 方法 PrintStart支援標準列印功能,逐一於主控台輸出報表列印過程的提示訊息,模擬報表輸出程序,最後呼叫另一個函式 FinishedWork 方法,輸出相關訊息提示報表輸出工作完成。

FinishedWork 函式接受參數 reportType ,代表目前列印的報表規格名稱,輸出訊息通知使用者結束執行的列印工作是何種規格的報表。

由於外部程式並不清楚何時列印工作完成,此方法只能由 PrintStart 方法緊接著於列印完成之後呼叫,因此將其限制為 protected 存取層級,只能由繼承的子類別引用,如此一來確保了 FinishedWork 方法只有在 PReport 繼承類別執行列印工作完成時才能執行。
接下來針對不同格式的報表的列印,建立子類別覆寫列印方法,輸出專屬的列印訊息。

//列印 A4 尺寸
class PrintA4 : PReport
{
    public override void PrintStart()
    {
        Console.WriteLine("報表開始列印...!!");
        Console.WriteLine("列印 A4 尺寸報表...!!");
        Console.WriteLine("A4 尺寸報表列印完成...!!");
        FinishedWork("A4 ");
    }
}
//列印 A3 尺寸
class PrintA3 : PReport
{
    public override void PrintStart()
    {
        Console.WriteLine("報表開始列印...!!");
        Console.WriteLine("列印 A3 尺寸報表...!!");
        Console.WriteLine("A3 尺寸報表列印完成...!!");
        FinishedWork("A3 ");
    }
}

PrintA4 與 PintA3 均繼承 PReport 類別,實作不同版本的報表列印功能,其中列印類別 PritnA4以關鍵字 override複寫 PrintStart() 方法,支援A4規格的紙張列印,列印 A3 格式報表的類別 PrintA3 實作了相同的功能。

完成類別設計,現在撰寫測試程式:

class Program
{
    static void Main()
    {
        do
        {
            int reportType;
            Console.WriteLine("請選擇所要列印的報表規格 !! ");
            Console.Write("1.標準 / 2.A4 尺寸 / 3.A3 尺寸  ? ");
            reportType = int.Parse(Console.ReadLine());
            Console.WriteLine("");

            switch (reportType)
            {
                case 1:
                    PReport preport = new PReport();
                    preport.PrintStart();
                    break;
                case 2:
                    PrintA4 pritnA4 = new PrintA4();
                    pritnA4.PrintStart();
                    break;
                case 3:
                    PrintA3 printA3 = new PrintA3();
                    printA3.PrintStart();
                    break;
            }
            Console.WriteLine("");
        } while (true);
    }
}

方法 Main 首先提示使用者選擇要列印的報表規格,根據輸入的代碼產生相關的類別實體物件,執行不同版本的 PrintStart() 方法,以下執行測試:

請選擇所要列印的報表規格 !!
1.標準 / 2.A4 尺寸 / 3.A3 尺寸 ? 1

報表列印程式啟動 ....
報表開始列印...!!
列印標準尺寸報表...!!
工作完成...!!
標準 報表列印完成...!!
清除佇列工作...!!

請選擇所要列印的報表規格 !!
1.標準 / 2.A4 尺寸 / 3.A3 尺寸 ? 2

報表列印程式啟動 ....
報表開始列印...!!
列印 A4 尺寸報表...!!
A4 尺寸報表列印完成...!!
A4 報表列印完成...!!
清除佇列工作...!!

請選擇所要列印的報表規格 !!
1.標準 / 2.A4 尺寸 / 3.A3 尺寸 ?

使用者輸入指定的印表格式,程式引用不同版本的覆寫方法,輸出列印結果。

現在嘗試將PReport 類別中的virtual 關鍵字移除如下:

    public void PrintStart()
    {
        Console.WriteLine("報表開始列印...!!");
        Console.WriteLine("列印標準尺寸報表...!!");
        Console.WriteLine("工作完成...!!");
        FinishedWork("標準");
    }

重新執行程式,會出現以下的錯誤訊息:

錯誤 CS0506 'PrintA3.PrintStart()': 無法覆寫繼承的成員 'PReport.PrintStart()',因為其未標記為 virtual、abstract 或 override。
錯誤 CS0506 'PrintA4.PrintStart()': 無法覆寫繼承的成員 'PReport.PrintStart()',因為其未標記為 virtual、abstract 或 override。

從這裏我們可以很清楚的理解 virtual 關鍵字與覆寫的關係。

另外要注意的是,當繼承的關係不只一層,只要最頂層的類別已標示為可覆寫,則所有繼承關系中的衍生類別都可以覆寫上一層基礎類別的方法。