iT邦幫忙

2023 iThome 鐵人賽

DAY 14
1

前言
在理解這個概念以前,抽象跟虛擬對最初是初學者的我來說很難分辨,所以特別來描述關於這兩個的觀念。


什麼是抽象方法?

抽象方法是一種只有方法的名稱和簽名(參數列表),但沒有實際方法內容的方法。
它是一個純粹的規範,告訴子類別(繼承它的類別)必須實作這個方法,但並不提供具體的實作方式。這樣的方法通常用關鍵字 abstract 來宣告,並且不包含方法主體(即方法內的程式碼)。

抽象方法的主要目的是為了讓子類別提供自己的實作,以滿足特定的需求。
換句話說,抽象方法建立了一個契約,要求所有繼承該抽象類別的子類別都必須提供自己的版本,以符合這個契約。

什麼是抽象類別?

抽象類別是包含了抽象方法的類別,而且抽象類別本身也可以包含具體的方法。與普通類別不同,抽象類別不能直接實例化為物件,只能被用作其他類別的基礎,也就是被繼承。

抽象類別通常用於定義一組相關的類別所需實作的方法,但這些方法的實際內容會因為每個具體的子類別而有所不同。換句話說,抽象類別提供了一個通用的框架,並要求子類別根據自己的需求來填充這個框架。

抽象類別的特徵包括:

  • 可以包含抽象方法(不提供實作)和具體方法(提供實作)。
  • 不能被實例化,只能被繼承。
  • 子類別必須實作所有的抽象方法,否則它們自己也必須標記為抽象類別。

抽象方法和抽象類別是用來建立一個規範或契約,以確保衍生類別提供特定功能的程式設計概念。
鼓勵程式碼的重複使用和規範性,同時也允許繼承者自由實作具體的細節,從而實現多型性和更好的程式設計結構。

現在可以想像你有一個大筆記本,這個筆記本是特別的,因為它的每一頁都有一個不同的圖畫,但這些圖畫都是未完成的。而筆記本的第一頁可能是一個未完成的車子,第二頁可能是一個未完成的房子,以此類推。
這本筆記本就像是一個抽象類別,每一頁就像是一個抽象方法。抽象方法就像是一張空白的畫,上面只有一些輪廓和線條,但還沒有被填色或完成。
你知道每一頁都必須被填色才能變成一幅完整的畫,但這本筆記本本身不能被展示或使用,因為它只是一堆未完成的畫面的集合。

那如果你想要創建一個新的畫,可以複製這本筆記本的一頁,然後在上面填色,讓它變成一幅獨特的畫。這就像是創建一個繼承自抽象類別的新類別,並實作抽象方法,讓它變成一個具體的類別。

總之,抽象類別就像是一本未完成的筆記本,其中每一頁都是未完成的畫。你需要從這本筆記本中複製一頁,然後填色,才能創建一幅完整的畫。這樣,小孩可以更容易地理解抽象類別的概念。

當使用抽象類別時,可以確保子類別實現特定的成員,示範如何使用抽象類別:

範例 1:

原始程式碼:

public class Storage
{
    public virtual void Save(string content)
    {
        // 存檔邏輯
    }
}

public class FileStorage : Storage { }

public class DatabaseStorage : Storage { }

修改後的程式碼,使用抽象方法,可以看到關鍵字abstract :

public abstract class Storage
{
    public abstract void Save(string content);
}

public class FileStorage : Storage
{
    public override void Save(string content)
    {
        // 存成實體檔案的邏輯
    }
}

public class DatabaseStorage : Storage
{
    public override void Save(string content)
    {
        // 存入資料庫的邏輯
    }
}

這樣,您可以確保任何繼承自 Storage 的子類別都必須實作 Save 方法,否則編譯將無法通過。

抽象類別(Storage)以及它的兩個子類別(FileStorageDatabaseStorage),用於儲存資料的不同方式。以下是每個部分的詳細解釋:

  1. Storage 抽象類別:
    • Storage 是一個抽象類別,使用 abstract 關鍵字來標記,表示它不能被實例化,只能用作其他類別的父類別。
    • 有一個抽象方法 Save(string content),這個方法沒有實際的程式碼實作,只有方法的簽名(名稱和參數)。派生類別必須實作這個方法。
  2. FileStorage 類別:
    • FileStorageStorage 抽象類別的一個子類別,使用 : 符號來指定它繼承自 Storage
    • 實作了 Save(string content) 方法,具體定義了將資料存儲為實體檔案的邏輯。也就是說,當您使用 FileStorage 類別的實例呼叫 Save 方法時,它會將提供的內容保存到檔案中。
  3. DatabaseStorage 類別:
    • DatabaseStorage 也是 Storage 抽象類別的一個派生類別,同樣使用 : 來繼承 Storage
    • 它實作了 Save(string content) 方法,但這次的實作是將提供的內容存入資料庫,而不是儲存為實體檔案。

這種結構允許您創建一個通用的 Storage 抽象類別,定義了資料儲存的通用界面,但不指定實際的儲存細節。然後,您可以創建具體的儲存方式,如 FileStorageDatabaseStorage,分別實作了 Save 方法以處理不同的儲存需求。這樣的設計使得程式碼具有彈性,可以根據需要輕鬆擴展和添加新的儲存方式。

範例 2:

原始程式碼:

public class Program
{
    static void Main(string[] args)
    {
        Storage obj = new Storage();
        obj.Save("hello world");
    }
}

public class Storage
{
    public void Save(string content)
    {
        // 存檔邏輯
    }
}

修改後的程式碼,使用抽象方法:

public class Program
{
    static void Main(string[] args)
    {
        Storage obj = new FileStorage();
        obj.Save("hello world");
    }
}

public abstract class Storage
{
    public abstract void Save(string content);
}

public class FileStorage : Storage
{
    public override void Save(string content)
    {
        // 存成實體檔案的邏輯
    }
}

在範例中,Storage 類別變成抽象類別,並且 Save 方法變成抽象方法,要求所有繼承的子類別實作它。並且,在 Main 方法中,我們實例化了 FileStorage 類別,並呼叫其 Save 方法,這樣您就可以在子類別中實作不同的存檔邏輯。

使用抽象類別和抽象方法可以讓您更清晰地定義規範,確保子類別遵守這些規範,並提供一致的介面,以便使用者或其他開發人員更容易理解和使用您的程式碼。

抽象方法(abstract method)和虛擬方法(virtual method)都是用於實現多型性(polymorphism)的概念,但它們在某些重要方面有一些不同之處。下面描述關於抽象方法和虛擬方法之間的主要區別:

1. 實作差異:

  • 抽象方法:抽象方法是一種完全沒有方法實作的方法。它只包含方法的簽名(名稱、參數列表),並使用 abstract 關鍵字聲明。抽象方法必須存在於抽象類別中,並且要求所有派生類別提供自己的具體實作。抽象方法強制衍生類別提供實作,以滿足特定的需求。
  • 虛擬方法:虛擬方法是有方法實作的方法,但可以在衍生類別中進行覆寫(override)。虛擬方法使用 virtual 關鍵字聲明,並在基底類別中提供默認實作。派生類別可以選擇性地覆寫虛擬方法,以修改或擴展其行為。如果不覆寫,將使用基底類別中的默認實作。

2. 適用範圍差異:

  • 抽象方法:抽象方法通常存在於抽象類別中,而抽象類別本身不能實例化。它們用於建立一個約定或契約,要求所有繼承它的子類別提供特定功能的實作。抽象方法的主要目的是確保一致性,並讓不同的子類別提供不同的實作。
  • 虛擬方法:虛擬方法存在於可以實例化的類別中,並且通常是基底類別中的一部分。它們允許派生類別選擇性地覆寫方法,以自訂行為。虛擬方法通常用於實現基本行為,並讓派生類別進行修改或擴展。

3. 覆寫差異:

  • 抽象方法:派生類別必須覆寫抽象方法,否則編譯器將產生錯誤。這強制子類別提供具體的實作,確保每個子類別都符合約定。
  • 虛擬方法:派生類別可以選擇性地覆寫虛擬方法,但不是必需的。如果不覆寫,將使用基底類別中的默認實作。這允許子類別根據需要修改或保留基底實作。

抽象方法主要用於強制子類別提供特定的實作存在於抽象類別中,而虛擬方法則用於允許子類別選擇性地修改或擴展基底類別的行為,存在於可以實例化的類別中。
選擇使用哪種方法取決於您的設計需求,以及是否需要確保一致性和實作的強制性。

那就舉例用形狀(Shape)和它具體子類別,像是矩形(Rectangle)和圓形(Circle),來詳細說明抽象方法和虛擬方法之間的區別。

首先,創建一個抽象類別 Shape,它包含一個抽象方法 CalculateArea,方法名稱如同其命名,用於計算形狀的面積。

public abstract class Shape
{
    public abstract double CalculateArea();
}

上面有一個抽象類別 Shape,裡面有一個抽象方法 CalculateArea,所有繼承 Shape 的子類別都必須實作這個方法。這就是抽象方法的特點,它強制子類別提供特定的實作。

現在,讓我們創建具體的子類別 RectangleCircle,這些子類別將繼承 Shape 並實作 CalculateArea 方法。

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public override double CalculateArea()
    {
        return Width * Height;
    }
}

public class Circle : Shape
{
    public double Radius { get; set; }

    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

Rectangle 和 Circle 類別都繼承自 Shape 並實作了 CalculateArea 方法,用來計算矩形和圓形的面積。

繼承之後,就可以開始創建這些形狀的實例,並呼叫 CalculateArea 方法來計算它們的面積:

static void Main()
{
    Shape rectangle = new Rectangle(5, 4);
    Shape circle = new Circle(3);

    Console.WriteLine($"Rectangle Area: {rectangle.CalculateArea()}");
    Console.WriteLine($"Circle Area: {circle.CalculateArea()}");
}

上面主要是使用抽象方法的使用。
在 Shape 基底類別中定義了一個抽象方法 CalculateArea ,子類別 Rectangle 和 Circle 必須實作這個方法,以提供具體的面積計算。

抽象的完成以後換來看看如果使用虛擬方法的話。
在這個情境下,我們修改父類別 Shape ,讓 CalculateArea 方法變成虛擬方法,而不是抽象方法,如下。

public class Shape
{
    public virtual double CalculateArea()
    {
        return 0.0; // 默認面積值
    }
}

這時候上面的 Shape 類別中的 CalculateArea 方法是虛擬的,並提供了一個默認的面積值(0.0)。這表示我們的子類別可以選擇性地覆寫這個方法,而不是強制性地提供實作。

再來看下面的 Rectangle 和 Circle 子類別,因為上面的方法是虛擬的,所以現在不再需要 override 關鍵字:

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public override double CalculateArea()
    {
        return Width * Height;
    }
}

public class Circle : Shape
{
    public double Radius { get; set; }

    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

可以看到 Rectangle 和 Circle 子類別都覆寫了 CalculateArea 方法,但這是選擇性的,因為 Shape 父類別提供了虛擬方法的默認實作。
虛擬方法允許子類別選擇性地覆寫基底方法,並修改或擴展其行為,而不需要強制性提供實作。這使得程式碼更靈活,並允許在需要時進行方法的自訂。

換方式說明他們之間的差異。

抽象方法:
抽象方法就像是一個留給小朋友的謎題,我們告訴小朋友「我們需要一個特別的答案,但我們不會告訴你是什麼答案,你必須自己找出答案。」這樣,小朋友必須去思考和猜測答案,每個人都可以給出自己的答案,但都必須遵循一些規則。

虛擬方法:
虛擬方法就像是一個故事的一部分,我們有一個主要故事,但有些小部分可以有不同的結局。這些小結局是虛擬的,也就是說,你可以自己決定要不要改變它們。如果你喜歡原來的故事,你可以不改變它們,但如果你想要一個不同的結局,你可以創造自己的結局。

總之,抽象方法就像是一個謎題,需要自己找出答案,而虛擬方法就像是一個故事中的選擇,你可以決定是否改變它。這兩種方法都讓程式更有彈性,可以根據需要自行調整。


挑戰第14天完成~


上一篇
Day 13 關鍵字 - return , params
下一篇
Day 15 命名空間
系列文
30天開啟.NET後端工程師的旅程30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言