MST

星途 面试题库

面试题:C#中接口和抽象类在多继承与依赖注入方面的应用探讨

在C#中,接口一定程度上实现了类似多继承的效果,而抽象类也在一些场景中发挥重要作用。请深入分析它们在多继承(接口体现的伪多继承特性)概念上的具体应用方式,以及在依赖注入框架中的角色和使用原理,并结合代码示例阐述如何在依赖注入场景下灵活运用接口和抽象类来实现松耦合、可维护性高的软件架构。
13.8万 热度难度
编程语言C#

知识考点

AI 面试

面试题答案

一键面试

接口和抽象类在多继承概念上的应用方式

  1. 接口的伪多继承特性
    • 在C#中,类不能直接继承多个类,但可以实现多个接口。接口只定义方法签名,不包含方法的实现(除了C# 8.0及以后可包含默认实现)。这使得一个类能从多个接口“继承”行为,模拟多继承效果。
    • 例如:
public interface IPrintable
{
    void Print();
}
public interface IStorable
{
    void Store();
}
public class Document : IPrintable, IStorable
{
    public void Print()
    {
        Console.WriteLine("Printing document...");
    }
    public void Store()
    {
        Console.WriteLine("Storing document...");
    }
}
  • 这里Document类实现了IPrintableIStorable接口,从而具备了两种不同的行为,类似从多个“父类”继承功能。
  1. 抽象类在多继承概念中的作用
    • 抽象类可以包含抽象方法和具体方法。抽象类主要用于定义一些具有共性的行为和属性,子类继承抽象类并实现抽象方法。虽然不能像接口那样实现“多继承”行为,但在继承体系中起到了重要的抽象和规范作用。
    • 例如:
public abstract class Shape
{
    public abstract double Area();
    public void PrintInfo()
    {
        Console.WriteLine($"This is a shape with area: {Area()}");
    }
}
public class Circle : Shape
{
    private double radius;
    public Circle(double radius)
    {
        this.radius = radius;
    }
    public override double Area()
    {
        return Math.PI * radius * radius;
    }
}
  • Shape抽象类定义了Area抽象方法和PrintInfo具体方法,Circle类继承Shape并实现Area方法,体现了抽象类在继承体系中的规范和复用作用。

在依赖注入框架中的角色和使用原理

  1. 接口在依赖注入中的角色和原理
    • 接口在依赖注入中起到了契约的作用。依赖注入框架通过接口来解析和注入具体的实现类。
    • 例如,使用Microsoft.Extensions.DependencyInjection
public interface ILogger
{
    void Log(string message);
}
public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine($"[ConsoleLogger] {message}");
    }
}
public class MyService
{
    private readonly ILogger logger;
    public MyService(ILogger logger)
    {
        this.logger = logger;
    }
    public void DoWork()
    {
        logger.Log("Doing some work...");
    }
}
class Program
{
    static void Main()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddTransient<ILogger, ConsoleLogger>();
        serviceCollection.AddTransient<MyService>();
        var serviceProvider = serviceCollection.BuildServiceProvider();
        var myService = serviceProvider.GetService<MyService>();
        myService.DoWork();
    }
}
  • 这里MyService依赖ILogger接口,依赖注入框架根据配置将ConsoleLogger注入到MyService中。通过接口,MyService不需要关心具体的日志实现类,实现了松耦合。
  1. 抽象类在依赖注入中的角色和原理
    • 抽象类在依赖注入中可用于定义一些通用的抽象行为,具体实现类继承抽象类。依赖注入框架同样可以根据抽象类类型来注入具体的子类实例。
    • 例如:
public abstract class LoggerBase
{
    public abstract void Log(string message);
}
public class FileLogger : LoggerBase
{
    public override void Log(string message)
    {
        File.AppendAllText("log.txt", $"[FileLogger] {message}\n");
    }
}
public class AnotherService
{
    private readonly LoggerBase logger;
    public AnotherService(LoggerBase logger)
    {
        this.logger = logger;
    }
    public void DoOtherWork()
    {
        logger.Log("Doing other work...");
    }
}
class Program2
{
    static void Main()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddTransient<LoggerBase, FileLogger>();
        serviceCollection.AddTransient<AnotherService>();
        var serviceProvider = serviceCollection.BuildServiceProvider();
        var anotherService = serviceProvider.GetService<AnotherService>();
        anotherService.DoOtherWork();
    }
}
  • AnotherService依赖LoggerBase抽象类,框架将FileLogger注入。抽象类在这里提供了一种抽象层次,使得具体实现类能在依赖注入中以统一的抽象类型被处理。

在依赖注入场景下灵活运用接口和抽象类实现松耦合、可维护性高的软件架构

  1. 使用接口实现松耦合
    • 接口定义了清晰的契约,不同模块通过接口进行交互。例如在一个分层架构中,业务逻辑层的服务可以依赖数据访问层的接口。
public interface IUserRepository
{
    User GetUserById(int id);
}
public class UserService
{
    private readonly IUserRepository userRepository;
    public UserService(IUserRepository userRepository)
    {
        this.userRepository = userRepository;
    }
    public User GetUser(int id)
    {
        return userRepository.GetUserById(id);
    }
}
  • 这样,业务逻辑层(UserService)与数据访问层的具体实现解耦,数据访问层可以有不同的实现类(如基于数据库、基于文件等),只需要实现IUserRepository接口即可,方便替换和维护。
  1. 结合抽象类和接口提高可维护性
    • 可以定义抽象类来提供一些通用的功能或属性,再结合接口来定义行为契约。
public abstract class RepositoryBase<T>
{
    // 一些通用的数据库连接等操作可以放在这里
    protected string connectionString;
    public RepositoryBase(string connectionString)
    {
        this.connectionString = connectionString;
    }
}
public interface IUserRepository : RepositoryBase<User>
{
    User GetUserById(int id);
}
public class SqlUserRepository : RepositoryBase<User>, IUserRepository
{
    public SqlUserRepository(string connectionString) : base(connectionString)
    {
    }
    public User GetUserById(int id)
    {
        // 使用 connectionString 进行数据库查询返回用户
        // 实际实现省略
        return null;
    }
}
  • 这里RepositoryBase抽象类提供了一些通用功能,IUserRepository接口定义了具体行为,SqlUserRepository继承抽象类并实现接口。这种方式既利用了抽象类的复用性,又通过接口实现了松耦合,提高了软件架构的可维护性。