昨天我們學習了繼承與多型,透過 virtual 與 override 的方式,讓不同的類別能夠展現不同的行為。今天我們要進一步探討兩個 OOP 的進階概念:介面(Interface) 與抽象類別(Abstract Class)。這兩者常常被初學者搞混,但其實他們解決的問題不同:
介面 (Interface) 定義了一組相關功能,但不包含實作。類別 (class) 或結構 (struct) 可以實作 (implement) 介面,並提供這些功能的具體實作。介面可包含:實例方法 (methods)、屬性 (properties)、事件 (events)、索引子 (indexers)。但介面不可包含:實例欄位 (fields)、實例建構子 (constructors)、解構子 (finalizers)。
C# 不支援多重繼承 (multiple inheritance),所以使用介面可以讓類別同時具備多種行為。
結構 (struct) 不能繼承其他 struct 或 class,但可以實作介面,所以這樣也能「模擬繼承」。
達到程式設計上的一致性,例如:任何實作 IEquatable<T>
的類別,都必須實作 Equals
方法。
使用 interface
關鍵字定義。
命名慣例:通常以大寫 I 開頭,例如 IEquatable<T>
。
interface IEquatable<T>
{
bool Equals(T obj);
}
public class Car : IEquatable<Car>
{
public string? Make { get; set; }
public string? Model { get; set; }
public string? Year { get; set; }
// 必須實作 IEquatable<T> 的 Equals 方法
public bool Equals(Car? car)
{
return (this.Make, this.Model, this.Year) ==
(car?.Make, car?.Model, car?.Year);
}
}
介面實作方式主要分為兩種:隱含實作 (Implicit Implementation)以及明確實作 (Explicit Implementation)。其中隱含實作 (Implicit Implementation)介面方法對應到 public、非 static 的類別方法,名稱與簽章相同,最常見的做法如下:
public class Logger : ILoggable
{
public void Log(string message) // public + 符合簽章
{
Console.WriteLine($"Log: {message}");
}
}
而在明確實作 (Explicit Implementation),使用介面名稱限定,方法前沒有存取修飾詞,僅能透過介面存取,常用於介面簽章中有 internal
或 private
類型時。
internal class InternalConfiguration
{
public string Setting { get; set; } = "";
}
internal interface IConfigurable
{
void Configure(InternalConfiguration config);
}
public class ServiceImplementation : IConfigurable
{
// 明確實作,方法前不加 public
void IConfigurable.Configure(InternalConfiguration config)
{
Console.WriteLine($"Configured with: {config.Setting}");
}
}
而其呼叫方式如下:
ServiceImplementation service = new ServiceImplementation();
// service.Configure(...) ❌ 不能直接呼叫
((IConfigurable)service).Configure(new InternalConfiguration { Setting = "ABC" }); ✅
介面可以繼承一個或多個介面,類別實作衍生介面時,必須同時實作所有基底介面的成員,範例如下:
interface IA
{
void A();
}
interface IB : IA
{
void B();
}
public class MyClass : IB
{
public void A() => Console.WriteLine("A");
public void B() => Console.WriteLine("B");
}
abstract
用於類別或成員(方法、屬性、索引子、事件),表示該成員 沒有完整實作,必須由衍生類別 (derived class) 來實作。
抽象類別 (abstract class):
abstract
方法或屬性,衍生類別必須 override
sealed
同時使用 (因為 sealed
代表不能被繼承)下面的範例示範了一個包含已實作方法和抽象成員的抽象類別:
namespace LanguageKeywords;
public abstract class Vehicle
{
protected string _brand;
// Constructor - implemented method in abstract class
public Vehicle(string brand) => _brand = brand;
// Implemented method - provides functionality that all vehicles share
public string GetInfo() => $"This is a {_brand} vehicle.";
// Another implemented method
public virtual void StartEngine() => Console.WriteLine($"{_brand} engine is starting...");
// Abstract method - must be implemented by derived classes
public abstract void Move();
// Abstract property - must be implemented by derived classes
public abstract int MaxSpeed { get; }
}
public class Car : Vehicle
{
public Car(string brand) : base(brand) { }
// Implementation of abstract method
public override void Move() => Console.WriteLine($"{_brand} car is driving on the road.");
// Implementation of abstract property
public override int MaxSpeed => 200;
}
public class Boat : Vehicle
{
public Boat(string brand) : base(brand) { }
// Implementation of abstract method
public override void Move() => Console.WriteLine($"{_brand} boat is sailing on the water.");
// Implementation of abstract property
public override int MaxSpeed => 50;
}
public class AbstractExample
{
public static void Examples()
{
// Cannot instantiate abstract class: Vehicle v = new Vehicle("Generic"); // Error!
Car car = new Car("Toyota");
Boat boat = new Boat("Yamaha");
// Using implemented methods from abstract class
Console.WriteLine(car.GetInfo());
car.StartEngine();
// Using abstract methods implemented in derived class
car.Move();
Console.WriteLine($"Max speed: {car.MaxSpeed} km/h");
Console.WriteLine();
Console.WriteLine(boat.GetInfo());
boat.StartEngine();
boat.Move();
Console.WriteLine($"Max speed: {boat.MaxSpeed} km/h");
}
}
class Program
{
static void Main()
{
AbstractExample.Examples();
}
}
/* Output:
This is a Toyota vehicle.
Toyota engine is starting...
Toyota car is driving on the road.
Max speed: 200 km/h
This is a Yamaha vehicle.
Yamaha engine is starting...
Yamaha boat is sailing on the water.
Max speed: 50 km/h
*/
從上面程式碼可以得到幾個重點:
在此範例中,Square
類別必須提供 GetArea
的實現,因為它衍生自Shape
:
abstract class Shape
{
public abstract int GetArea();
}
class Square : Shape
{
private int _side;
public Square(int n) => _side = n;
// GetArea method is required to avoid a compile-time error.
public override int GetArea() => _side * _side;
static void Main()
{
var sq = new Square(12);
Console.WriteLine($"Area of the square = {sq.GetArea()}");
}
}
// Output: Area of the square = 144
其中
abstract class Shape
{
public abstract int GetArea();
}
這是一個抽象類別,不能直接創建實例,且定義了一個抽象方法GetArea()
,沒有實現(沒有大括號 {} 的內容,任何繼承Shape
的類別都必須實現這個方法。
從以上程式碼可以得到幾個重點:
在這個例子中,類別 DerivedClass
衍生自抽象類別 BaseClass
。此抽象類別包含一個抽象方法 AbstractMethod
和兩個抽象屬性 X
和 Y
。
// Abstract class
abstract class BaseClass
{
protected int _x = 100;
protected int _y = 150;
// Abstract method
public abstract void AbstractMethod();
// Abstract properties
public abstract int X { get; }
public abstract int Y { get; }
}
class DerivedClass : BaseClass
{
public override void AbstractMethod()
{
_x++;
_y++;
}
public override int X // overriding property
{
get
{
return _x + 10;
}
}
public override int Y // overriding property
{
get
{
return _y + 10;
}
}
static void Main()
{
var o = new DerivedClass();
o.AbstractMethod();
Console.WriteLine($"x = {o.X}, y = {o.Y}");
}
}
// Output: x = 111, y = 161
由於X的初始值是100、Y的初始值是150,在_x++;_y++;這裡分別變成X:101 Y:151,到後面return _x + 10;return _y + 10;,所以得到Output: x = 111, y = 161。
從上方程式碼內容得到的重點:
public abstract class Shape
{
public string Color { get; set; }
// Constructor of the abstract class
protected Shape(string color)
{
Color = color;
Console.WriteLine($"Created a shape with color {color}.");
}
// Abstract method that must be implemented by derived classes
public abstract double CalculateArea();
}
public class Square : Shape
{
public double Side { get; set; }
// Constructor of the derived class calling the base class constructor
public Square(string color, double side) : base(color)
{
Side = side;
}
public override double CalculateArea()
{
return Side * Side;
}
}
public class Program
{
public static void Main(string[] args)
{
Square square = new Square("red", 5);
Console.WriteLine($"Area of the square: {square.CalculateArea()}");
}
}
結果如下圖:
從上面的程式碼內容可以得到以下重點:
建構函式鏈:使用 : base(參數) 調用父類的建構函式,父類的建構函式會先執行
封裝:使用屬性(Property)來封裝欄位(Field),提供更好的控制和驗證