编辑
2025-02-03
C# 应用
00
请注意,本文编写于 92 天前,最后修改于 92 天前,其中某些信息可能已经过时。

目录

抽象类与接口的基本区别
抽象类
接口
抽象类与接口的相似之处
抽象类与接口的关键区别
何时使用抽象类
状态管理
访问修饰符
构造函数和析构函数
何时使用接口
多重实现
模块化
模拟
设计灵活性
总结

在设计应用程序时,了解何时使用抽象类和何时使用接口是至关重要的。尽管抽象类和接口在某些方面看起来相似,但它们之间存在关键差异,这些差异将决定哪种选择最适合您要实现的目标。

image.png

抽象类与接口的基本区别

抽象类

抽象类是一种特殊类型的类,不能被实例化。抽象类旨在被子类继承,这些子类可以实现或重写其方法。换句话说,抽象类可以是部分实现的,也可以完全不实现。抽象类可以包含具体方法和抽象方法,并且可以有构造函数。

C#
public abstract class Animal { public abstract void MakeSound(); public void Sleep() { Console.WriteLine("Sleeping..."); } } public class Dog : Animal { public override void MakeSound() { Console.WriteLine("Bark"); } }

接口

接口基本上是一个契约,它不包含任何实现。接口只能包含方法声明,不能包含方法定义,也不能包含成员数据。实现接口的类必须实现接口中的所有成员。

C#
public interface IAnimal { void MakeSound(); } public class Dog : IAnimal { public void MakeSound() { Console.WriteLine("Bark"); } }

抽象类与接口的相似之处

  1. 行为定义而非实现:C# 提供了接口和抽象类来定义行为而不实现。可以利用接口或抽象类来定义一个契约,必须由扩展或实现该接口或抽象类的类型遵循。
  2. 不可实例化:抽象类和接口都不能被实例化,必须由其他类型实现或扩展。
  3. 支持多态性:可以使用接口和抽象类实现多态性。
  4. 访问修饰符:接口和抽象类中的方法都可以包含访问修饰符。

抽象类与接口的关键区别

  1. 默认实现:抽象类可以有默认实现的方法,而接口直到 C# 8.0 才支持其成员的默认实现。
  2. 成员类型:抽象类可以有字段、构造函数、析构函数和静态成员,而接口不能有这些成员。
  3. 状态信息:抽象类可以有状态信息(即字段),而接口不能有实例字段。
  4. 继承:一个类只能继承一个抽象类,但可以实现多个接口。

何时使用抽象类

状态管理

如果需要管理状态信息,可以使用抽象类。抽象类可以包含字段,子类可以访问这些字段。

C#
public abstract class Animal { protected string name; public Animal(string name) { this.name = name; } public abstract void MakeSound(); } public class Dog : Animal { public Dog(string name) : base(name) { } public override void MakeSound() { Console.WriteLine($"{name} says: Bark"); } }

访问修饰符

抽象类可以使用访问修饰符来提供对其成员的细粒度控制。

C#
public abstract class Animal { protected string name; public Animal(string name) { this.name = name; } public abstract void MakeSound(); protected void Sleep() { Console.WriteLine("Sleeping..."); } } public class Dog : Animal { public Dog(string name) : base(name) { } public override void MakeSound() { Console.WriteLine($"{name} says: Bark"); } }

构造函数和析构函数

抽象类可以有构造函数和析构函数,允许初始化其成员并编写清理逻辑的代码。

C#
public abstract class Animal { protected string name; public Animal(string name) { this.name = name; } public abstract void MakeSound(); ~Animal() { Console.WriteLine($"{name} is being destroyed"); } } public class Dog : Animal { public Dog(string name) : base(name) { } public override void MakeSound() { Console.WriteLine($"{name} says: Bark"); } }

何时使用接口

多重实现

当需要在应用程序中建立契约而不强制直接继承时,接口是首选。一个类可以实现多个接口,从而允许它同时符合多个契约。

C#
public interface IAnimal { void MakeSound(); } public interface IPet { void Play(); } public class Dog : IAnimal, IPet { public void MakeSound() { Console.WriteLine("Bark"); } public void Play() { Console.WriteLine("Playing..."); } }

模块化

接口促进了一种设计,在这种设计中,定义了一个契约,任何实现该契约的类都必须遵循,从而促进了模块化、可扩展和灵活的架构。

C#
public interface IRepository<T> { void Add(T item); T Get(int id); } public class Repository<T> : IRepository<T> { public void Add(T item) { // Implementation } public T Get(int id) { // Implementation return default(T); } }

模拟

在编写单元测试时,可以使用接口创建模拟对象。因为可以在接口中定义行为而不实现,所以可以使用接口创建不依赖于具体类实现的单元测试。

C#
public interface IAnimal { void MakeSound(); } public class AnimalMock : IAnimal { public void MakeSound() { Console.WriteLine("Mock sound"); } }

设计灵活性

可以利用接口创建具有演变设计的应用程序,通过编程接口而不是其实现,可以处理抽象而不是具体类,从而促进组件的可互换性。

C#
public interface IAnimal { void MakeSound(); } public class Dog : IAnimal { public void MakeSound() { Console.WriteLine("Bark"); } } public class Cat : IAnimal { public void MakeSound() { Console.WriteLine("Meow"); } } public class AnimalService { private readonly IAnimal _animal; public AnimalService(IAnimal animal) { _animal = animal; } public void MakeAnimalSound() { _animal.MakeSound(); } }

总结

抽象类和接口各有优缺点,选择哪一个取决于具体的需求。如果需要定义状态信息并指定字段的初始化方式,应该使用抽象类。如果需要定义一个行为契约而不强制继承关系,应该使用接口。通过理解它们的区别和应用场景,可以设计出松耦合和可扩展的应用程序。

本文作者:rick

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!