跳至頁尾內容
Iron Academy Logo
學習C#
學習C#

其他類別

理解C#中的面向物件程式設計

Tim Corey
49m 41s

繼承和介面是物件導向程式設計 (OOP) 的重要組成部分。 Tim Corey在他的影片"繼承與介面在 C# 中的比較:物件導向程式設計"中,詳細解釋了何時使用繼承以及何時選擇介面。

這篇文章是Tim Corey影片的綜合指南。 它分解了影片中提供的關鍵概念、範例和程式碼解釋,強調了繼承與介面的差異以及何時使用各自的情況。

介紹

Tim在 (0:00) 開始強調區分繼承與介面的重要性。 他重申理解何時使用每個概念以獲得最佳效果的重要性。 他的目標是通過範例展示這一點,首先從單一繼承的錯誤使用開始,然後進行修正。

建立專案

在 (1:08),Tim使用 .NET 5 建立了一個簡單的主控台應用程式。他將專案命名為 "OODemoApp",並解釋其主要目的是演示概念而非建立適合生產的程式碼。

理解繼承

Tim在 (1:55) 探討了繼承的基礎。 他將繼承定義為一種機制,基類的屬性和方法由衍生類繼承。 他強調,繼承不應僅用於程式碼重用和共享,而應用於建立邏輯上的 "是-一" 關係。

關鍵點:

  • 是-一 關係:確保衍生類是基類的一種型別。
  • 共通邏輯:繼承的類別應共享共通邏輯,而不僅僅是屬性或方法簽名。

範例類別:租車

Tim在 (7:52) 建立了一個 RentalCar 類別,以說明繼承的基本概念。 此類別代表佛羅里達州邁阿密租車機構中的一輛出租車。

public class RentalCar
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public int NumberOfPassengers { get; set; }

    public void StartEngine()
    {
        Console.WriteLine("Turn key to ignition setting");
        Console.WriteLine("Turn key to on");
    }

    public void StopEngine()
    {
        Console.WriteLine("Turn key to off");
    }
}
public class RentalCar
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public int NumberOfPassengers { get; set; }

    public void StartEngine()
    {
        Console.WriteLine("Turn key to ignition setting");
        Console.WriteLine("Turn key to on");
    }

    public void StopEngine()
    {
        Console.WriteLine("Turn key to off");
    }
}

展示不正確繼承的陷阱

Tim在 (10:15) 解釋了不當使用繼承可能引發的問題。 他強調,如果錯誤運用繼承,可能導致難以管理和擴展的程式碼。 他建議不要僅僅為了分享程式碼而使用繼承。

為汽車和卡車型別新增列舉

Tim在 (10:45) 新增了一個汽車型別的列舉。他建立了一個名為 Enums.cs 的新類別文件,以便所有列舉都集中在一處。 此列舉將幫助區分不同的汽車樣式。

// Enums.cs
public enum CarType
{
    Hatchback,
    Sedan,
    Compact
}
// Enums.cs
public enum CarType
{
    Hatchback,
    Sedan,
    Compact
}

然後,他在 RentalCar 類中新增了一個屬性,以指明汽車型別。

public class RentalCar : RentalVehicle
{
    public CarType Style { get; set; }
    // Other properties and methods
}
public class RentalCar : RentalVehicle
{
    public CarType Style { get; set; }
    // Other properties and methods
}

擴展以包含卡車

如 Tim 在 (12:27) 解釋的那樣,租車公司決定將卡車納入其車隊,這帶來了新的需求。 他建立了一個從父類 RentalVehicle 繼承的 RentalTruck 類。

public class RentalTruck : RentalVehicle
{
    public TruckType Style { get; set; }
    // Other properties and methods
}
public class RentalTruck : RentalVehicle
{
    public TruckType Style { get; set; }
    // Other properties and methods
}

然後,他定義了一個新的卡車型別的列舉。

// Enums.cs
public enum TruckType
{
    ShortBed,
    LongBed
}
// Enums.cs
public enum TruckType
{
    ShortBed,
    LongBed
}

處理不同的屬性型別

Tim在 (15:28) 強調,僅僅因為兩個屬性有相同的名稱並不意味著它們是相同的。 他用 Style 屬性說明了這一點,這對汽車來說可能意味著不同的列舉(CarType 對汽車而言,而對卡車來說則是 TruckType)。

引入船隻到車隊

租車公司擴展其車隊以包括船隻。 Tim演示如何通過建立一個 RentalBoat 類進行處理。 最初,這似乎是可控的,因為船隻可以與汽車和卡車共享一些屬性。

public class RentalBoat : RentalVehicle
{
    // Properties and methods specific to boats
}
public class RentalBoat : RentalVehicle
{
    // Properties and methods specific to boats
}

處理帆船

引入帆船帶來了挑戰,因為帆船沒有引擎。 Tim在 (19:57) 中說明了在這種情況下繼承的限制。

public class RentalSailboat : RentalVehicle
{
    public override void StartEngine()
    {
        throw new NotImplementedException("I do not have an engine to start");
    }

    public override void StopEngine()
    {
        throw new NotImplementedException("I do not have an engine to stop");
    }
}
public class RentalSailboat : RentalVehicle
{
    public override void StartEngine()
    {
        throw new NotImplementedException("I do not have an engine to start");
    }

    public override void StopEngine()
    {
        throw new NotImplementedException("I do not have an engine to stop");
    }
}

用介面解決問題

Tim建議在基類中將 StartEngine 和 StopEngine 方法設為虛擬的,以允許在不使用這些方法的衍生類中重載。

public abstract class RentalVehicle
{
    // Common properties

    public virtual void StartEngine()
    {
        Console.WriteLine("Engine started");
    }

    public virtual void StopEngine()
    {
        Console.WriteLine("Engine stopped");
    }
}
public abstract class RentalVehicle
{
    // Common properties

    public virtual void StartEngine()
    {
        Console.WriteLine("Engine started");
    }

    public virtual void StopEngine()
    {
        Console.WriteLine("Engine stopped");
    }
}

處理不應調用的方法

Tim在 (21:56) 解釋存在於繼承類中的應避免調用的方法的陷阱。 例如,不具備引擎的 RentalSailboat 類,從 RentalVehicle 類繼承了 StartEngine 和 StopEngine 方法。 若誤調用這些方法可能會引發問題,因為這些方法必須拋出例外,以表明它們不適用。

public class RentalSailboat : RentalVehicle
{
    public override void StartEngine()
    {
        throw new NotImplementedException("I do not have an engine to start");
    }

    public override void StopEngine()
    {
        throw new NotImplementedException("I do not have an engine to stop");
    }
}
public class RentalSailboat : RentalVehicle
{
    public override void StartEngine()
    {
        throw new NotImplementedException("I do not have an engine to start");
    }

    public override void StopEngine()
    {
        throw new NotImplementedException("I do not have an engine to stop");
    }
}

認識繼承的限制

Tim在 (24:06) 強調繼承若不再符合邏輯,會導致程式碼庫雜亂無章。 例如,帆船不應被視作具備引擎的 RentalVehicle。這顯示了繼承的限制性,以及需要更好的設計方針。

用介面改進設計

為了解決這些問題,Tim 建議使用介面進行更好的設計。 他開始通過建立一個名為 "BetterOODemo" 的新主控台應用程式項目,來示範改進的方法。

定義介面

Tim引入IRental介面以封裝所有租用共享的屬性。

public interface IRental
{
    int RentalId { get; set; }
    string CurrentRenter { get; set; }
    decimal PricePerDay { get; set; }
}
public interface IRental
{
    int RentalId { get; set; }
    string CurrentRenter { get; set; }
    decimal PricePerDay { get; set; }
}

建立陸地交通工具的基類

Tim接著建立了一個陸地交通工具的基類,將交通工具租賃的概念與交通工具本身分開。

public abstract class LandVehicle
{
    public int NumberOfPassengers { get; set; }

    public virtual void StartEngine()
    {
        Console.WriteLine("Engine started");
    }

    public virtual void StopEngine()
    {
        Console.WriteLine("Engine stopped");
    }
}
public abstract class LandVehicle
{
    public int NumberOfPassengers { get; set; }

    public virtual void StartEngine()
    {
        Console.WriteLine("Engine started");
    }

    public virtual void StopEngine()
    {
        Console.WriteLine("Engine stopped");
    }
}

將基礎交通工具類重新命名為 LandVehicle,Tim 確保只有合適的車輛繼承與引擎相關的方法。

實現汽車和卡車類

Tim建立汽車和卡車類,這些類繼承自 LandVehicle 並實現 IRental 介面。

public class Car : LandVehicle, IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public CarType Style { get; set; }
}

public class Truck : LandVehicle, IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public TruckType Style { get; set; }
}
public class Car : LandVehicle, IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public CarType Style { get; set; }
}

public class Truck : LandVehicle, IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public TruckType Style { get; set; }
}

此設計保持了關注點的清晰分離,確保每個類僅獲得與自身型別相關的屬性和方法。

避免程式碼重複

Tim在 (31:41) 談到了避免不必要程式碼重複的重要性。 他解釋說,儘管 IRental 介面要求在多個類中使用相同的屬性,但這不被視為違反 DRY(Don't Repeat Yourself)原則,因為未重複任何邏輯—僅是屬性聲明。

實現租賃帆船類

Tim在 (35:09) 解釋如何通過實現 IRental 介面而非繼承 LandVehicle 單獨處理 RentalSailboat 類。 這種方法有助於避免不當繼承相關的問題。

public class Sailboat : IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }

    // Additional properties and methods specific to sailboats
}
public class Sailboat : IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }

    // Additional properties and methods specific to sailboats
}

管理不同的租賃

Tim設置了一個列表來管理不同型別的租賃,利用 IRental 介面來儲存各種租賃型別,包括卡車、帆船和汽車。

List<IRental> rentals = new List<IRental>
{
    new Truck { CurrentRenter = "Truck Renter" },
    new Sailboat { CurrentRenter = "Sailboat Renter" },
    new Car { CurrentRenter = "Car Renter" }
};
List<IRental> rentals = new List<IRental>
{
    new Truck { CurrentRenter = "Truck Renter" },
    new Sailboat { CurrentRenter = "Sailboat Renter" },
    new Car { CurrentRenter = "Car Renter" }
};

此設計允許迴圈遍歷租賃並存取共通屬性如 CurrentRenter, PricePerDayRentalId

foreach (var rental in rentals)
{
    Console.WriteLine($"Renter: {rental.CurrentRenter}, Price Per Day: {rental.PricePerDay}");
}
foreach (var rental in rentals)
{
    Console.WriteLine($"Renter: {rental.CurrentRenter}, Price Per Day: {rental.PricePerDay}");
}

型別專屬轉型

要存取不同租賃型別的特定屬性和方法,Tim演示如何使用 is 關鍵字來檢查並將物件轉型為各自的型別。

foreach (var rental in rentals)
{
    if (rental is Truck truck)
    {
        Console.WriteLine($"Truck Style: {truck.Style}, Passengers: {truck.NumberOfPassengers}");
    }
    else if (rental is Sailboat sailboat)
    {
        // Access sailboat-specific properties
    }
    else if (rental is Car car)
    {
        // Access car-specific properties
    }
}
foreach (var rental in rentals)
{
    if (rental is Truck truck)
    {
        Console.WriteLine($"Truck Style: {truck.Style}, Passengers: {truck.NumberOfPassengers}");
    }
    else if (rental is Sailboat sailboat)
    {
        // Access sailboat-specific properties
    }
    else if (rental is Car car)
    {
        // Access car-specific properties
    }
}

介面的靈活性

Tim強調使用介面提供後續變更的靈活性。 例如,新增坦克或電視等新的租賃型別,不會擾亂現有結構。

避免濫用繼承

Tim建議不要過度使用繼承來分享程式碼,因為這可能導致複雜且不靈活的程式碼庫。 反之,他建議利用介面和組合來達成所需結果,而不致將 "是-一" 的關係延伸超出其邏輯範圍。

結論

Tim Corey對OOP中繼承與介面的解釋,提供了建立可維護與靈活的程式碼的明確道路。 通過展示常見陷阱並提供使用介面的精煉設計,他確保開發者可以做出明智的決策來有效地構建程式。

要深入了解這些概念並看它們的實際應用,請欣賞 Tim 的完整影片。 他的頻道是程式設計教學的寶庫。 不要錯過!

Hero Worlddot related to 理解C#中的面向物件程式設計
Hero Affiliate related to 理解C#中的面向物件程式設計

分享您所愛以賺取更多報酬

您是否為使用 .NET、C#、Java、Python 或 Node.js 的開發者建立內容?將您的專業知識轉化為額外收入!

Iron 支援團隊

我們線上24小時,每週5天。
聊天
電子郵件
給我打電話