You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中依赖注入多接口类的优雅处理方案(以Renci.SshNet为例)

解决方案

方案1:自定义整合接口+适配器模式

这是最符合SOLID原则、测试友好的方案,彻底解决类型转换的问题:

  1. 定义整合接口:创建一个同时继承ISftpClient和IBaseClient的接口,利用C#多接口继承特性:
public interface IEnhancedSftpClient : ISftpClient, IBaseClient
{
    // 无需新增方法,自动继承两个接口的所有成员
}
  1. 实现适配器类:包装原生SftpClient,实现上述整合接口,所有方法直接转发给内部的SftpClient实例:
public class SftpClientAdapter : IEnhancedSftpClient
{
    private readonly SftpClient _innerClient;

    public SftpClientAdapter(SftpClient innerClient)
    {
        _innerClient = innerClient ?? throw new ArgumentNullException(nameof(innerClient));
    }

    // 实现ISftpClient的方法,直接转发
    public void UploadFile(Stream input, string path) => _innerClient.UploadFile(input, path);
    public Stream DownloadFile(string path) => _innerClient.DownloadFile(path);
    // ... 按需实现其他ISftpClient方法

    // 实现IBaseClient的方法,直接转发
    public void Connect() => _innerClient.Connect();
    public void Disconnect() => _innerClient.Disconnect();
    public bool IsConnected => _innerClient.IsConnected;
    public ConnectionInfo ConnectionInfo => _innerClient.ConnectionInfo;
    // ... 按需实现其他IBaseClient方法

    // 实现IDisposable接口
    public void Dispose() => _innerClient.Dispose();
}
  1. 依赖注入配置:注册适配器和原生客户端,让业务类依赖IEnhancedSftpClient:
// 注册原生SftpClient(根据你的配置调整)
services.AddScoped<SftpClient>(sp => 
    new SftpClient("host", "username", "password"));

// 注册适配器作为IEnhancedSftpClient的实现
services.AddScoped<IEnhancedSftpClient>(sp => 
    new SftpClientAdapter(sp.GetRequiredService<SftpClient>()));
  1. 业务类使用:直接注入IEnhancedSftpClient,无需任何类型转换:
public class SftpFileUploader
{
    private readonly IEnhancedSftpClient _sftpClient;

    public SftpFileUploader(IEnhancedSftpClient sftpClient)
    {
        _sftpClient = sftpClient;
    }

    public void Upload(Stream fileStream, string remotePath)
    {
        if (!_sftpClient.IsConnected)
        {
            _sftpClient.Connect();
        }
        _sftpClient.UploadFile(fileStream, remotePath);
        _sftpClient.Disconnect();
    }
}

优点:完全消除类型转换,测试时可轻松mockIEnhancedSftpClient,无需依赖Renci的具体实现;代码结构清晰,符合依赖反转原则。
缺点:需要编写适配器代码,若接口方法较多,会有一定样板代码量。

方案2:扩展方法封装类型转换逻辑

如果不想编写大量适配器代码,可以用扩展方法把类型转换逻辑封装起来,让业务代码更简洁:

public static class SftpClientExtensions
{
    public static void Connect(this ISftpClient client)
    {
        if (client is not IBaseClient baseClient)
        {
            throw new InvalidOperationException("当前SFTP客户端实例未实现IBaseClient接口");
        }
        baseClient.Connect();
    }

    public static void Disconnect(this ISftpClient client)
    {
        if (client is not IBaseClient baseClient)
        {
            throw new InvalidOperationException("当前SFTP客户端实例未实现IBaseClient接口");
        }
        baseClient.Disconnect();
    }

    // 按需添加IBaseClient的其他方法扩展
    public static bool IsConnected(this ISftpClient client)
    {
        return client is IBaseClient baseClient && baseClient.IsConnected;
    }
}

业务类使用:直接调用扩展方法,无需显式转换:

public void Upload(Stream fileStream, string remotePath)
{
    if (!_sftpClient.IsConnected())
    {
        _sftpClient.Connect();
    }
    _sftpClient.UploadFile(fileStream, remotePath);
    _sftpClient.Disconnect();
}

优点:代码改动极小,几乎不需要修改现有注入逻辑;业务代码保持简洁。
缺点:本质还是依赖类型转换,测试时若使用不实现IBaseClient的ISftpClient mock实例,会抛出异常(实际场景中Renci的SftpClient是实现该接口的,测试时可让mock同时实现两个接口)。

方案对比与推荐

  • 若项目注重测试性和代码架构的长期可维护性,方案1是最优选择,彻底解耦了对Renci客户端具体实现的依赖。
  • 若只是想快速优化现有代码,不想引入过多结构变化,方案2更适合,以最小改动解决代码优雅性问题。

内容的提问来源于stack exchange,提问作者Brian Kessler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 07:05:41