Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

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 Property RentalId As Integer
Public Property CurrentRenter As String
Public Property PricePerDay As Decimal
Public Property NumberOfPassengers As Integer
Public Sub StartEngine()
Console.WriteLine("Turn key to ignition setting")
Console.WriteLine("Turn key to on")
End Sub
Public Sub StopEngine()
Console.WriteLine("Turn key to off")
End Sub
End ClassTim在 (10:15) 解釋了不當使用繼承可能引發的問題。 他強調,如果錯誤運用繼承,可能導致難以管理和擴展的程式碼。 他建議不要僅僅為了分享程式碼而使用繼承。
Tim在 (10:45) 新增了一個汽車型別的列舉。他建立了一個名為 Enums.cs 的新類別文件,以便所有列舉都集中在一處。 此列舉將幫助區分不同的汽車樣式。
// Enums.cs
public enum CarType
{
Hatchback,
Sedan,
Compact
}' Enums.vb
Public Enum CarType
Hatchback
Sedan
Compact
End Enum然後,他在 RentalCar 類中新增了一個屬性,以指明汽車型別。
public class RentalCar : RentalVehicle
{
public CarType Style { get; set; }
// Other properties and methods
}Public Class RentalCar
Inherits RentalVehicle
Public Property Style As CarType
' Other properties and methods
End Class如 Tim 在 (12:27) 解釋的那樣,租車公司決定將卡車納入其車隊,這帶來了新的需求。 他建立了一個從父類 RentalVehicle 繼承的 RentalTruck 類。
public class RentalTruck : RentalVehicle
{
public TruckType Style { get; set; }
// Other properties and methods
}Public Class RentalTruck
Inherits RentalVehicle
Public Property Style As TruckType
' Other properties and methods
End Class然後,他定義了一個新的卡車型別的列舉。
// Enums.cs
public enum TruckType
{
ShortBed,
LongBed
}' Enums.vb
Public Enum TruckType
ShortBed
LongBed
End EnumTim在 (15:28) 強調,僅僅因為兩個屬性有相同的名稱並不意味著它們是相同的。 他用 Style 屬性說明了這一點,這對汽車來說可能意味著不同的列舉(CarType 對汽車而言,而對卡車來說則是 TruckType)。
租車公司擴展其車隊以包括船隻。 Tim演示如何通過建立一個 RentalBoat 類進行處理。 最初,這似乎是可控的,因為船隻可以與汽車和卡車共享一些屬性。
public class RentalBoat : RentalVehicle
{
// Properties and methods specific to boats
}Public Class RentalBoat
Inherits RentalVehicle
' Properties and methods specific to boats
End Class引入帆船帶來了挑戰,因為帆船沒有引擎。 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
Inherits RentalVehicle
Public Overrides Sub StartEngine()
Throw New NotImplementedException("I do not have an engine to start")
End Sub
Public Overrides Sub StopEngine()
Throw New NotImplementedException("I do not have an engine to stop")
End Sub
End ClassTim建議在基類中將 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 MustInherit Class RentalVehicle
' Common properties
Public Overridable Sub StartEngine()
Console.WriteLine("Engine started")
End Sub
Public Overridable Sub StopEngine()
Console.WriteLine("Engine stopped")
End Sub
End ClassTim在 (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
Inherits RentalVehicle
Public Overrides Sub StartEngine()
Throw New NotImplementedException("I do not have an engine to start")
End Sub
Public Overrides Sub StopEngine()
Throw New NotImplementedException("I do not have an engine to stop")
End Sub
End ClassTim在 (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
Property RentalId As Integer
Property CurrentRenter As String
Property PricePerDay As Decimal
End InterfaceTim接著建立了一個陸地交通工具的基類,將交通工具租賃的概念與交通工具本身分開。
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 MustInherit Class LandVehicle
Public Property NumberOfPassengers As Integer
Public Overridable Sub StartEngine()
Console.WriteLine("Engine started")
End Sub
Public Overridable Sub StopEngine()
Console.WriteLine("Engine stopped")
End Sub
End Class將基礎交通工具類重新命名為 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
Inherits LandVehicle
Implements IRental
Public Property RentalId As Integer Implements IRental.RentalId
Public Property CurrentRenter As String Implements IRental.CurrentRenter
Public Property PricePerDay As Decimal Implements IRental.PricePerDay
Public Property Style As CarType
End Class
Public Class Truck
Inherits LandVehicle
Implements IRental
Public Property RentalId As Integer Implements IRental.RentalId
Public Property CurrentRenter As String Implements IRental.CurrentRenter
Public Property PricePerDay As Decimal Implements IRental.PricePerDay
Public Property Style As TruckType
End Class此設計保持了關注點的清晰分離,確保每個類僅獲得與自身型別相關的屬性和方法。
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
Implements IRental
Public Property RentalId As Integer
Public Property CurrentRenter As String
Public Property PricePerDay As Decimal
' Additional properties and methods specific to sailboats
End ClassTim設置了一個列表來管理不同型別的租賃,利用 IRental 介面來儲存各種租賃型別,包括卡車、帆船和汽車。
List<IRental> rentals = new List<IRental>
{
new Truck { CurrentRenter = "Truck Renter" },
new Sailboat { CurrentRenter = "Sailboat Renter" },
new Car { CurrentRenter = "Car Renter" }
};Dim rentals As New List(Of IRental) From {
New Truck With {.CurrentRenter = "Truck Renter"},
New Sailboat With {.CurrentRenter = "Sailboat Renter"},
New Car With {.CurrentRenter = "Car Renter"}
}此設計允許迴圈遍歷租賃並存取共通屬性如 CurrentRenter, PricePerDay 和 RentalId。
foreach (var rental in rentals)
{
Console.WriteLine($"Renter: {rental.CurrentRenter}, Price Per Day: {rental.PricePerDay}");
}For Each rental In rentals
Console.WriteLine($"Renter: {rental.CurrentRenter}, Price Per Day: {rental.PricePerDay}")
Next要存取不同租賃型別的特定屬性和方法,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
}
}For Each rental In rentals
If TypeOf rental Is Truck Then
Dim truck As Truck = CType(rental, Truck)
Console.WriteLine($"Truck Style: {truck.Style}, Passengers: {truck.NumberOfPassengers}")
ElseIf TypeOf rental Is Sailboat Then
Dim sailboat As Sailboat = CType(rental, Sailboat)
' Access sailboat-specific properties
ElseIf TypeOf rental Is Car Then
Dim car As Car = CType(rental, Car)
' Access car-specific properties
End If
NextTim強調使用介面提供後續變更的靈活性。 例如,新增坦克或電視等新的租賃型別,不會擾亂現有結構。
Tim建議不要過度使用繼承來分享程式碼,因為這可能導致複雜且不靈活的程式碼庫。 反之,他建議利用介面和組合來達成所需結果,而不致將 "是-一" 的關係延伸超出其邏輯範圍。
Tim Corey對OOP中繼承與介面的解釋,提供了建立可維護與靈活的程式碼的明確道路。 通過展示常見陷阱並提供使用介面的精煉設計,他確保開發者可以做出明智的決策來有效地構建程式。
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy