目录

C#-interface使用

目录

1. 接口的本质认知

1.1 接口不只是"隔离变化"

很多开发者认为接口的作用就是"隔离变化",这个理解是对的,但不完整。

更准确地说,接口是:

维度 说明
契约 调用方只关心"你能做什么"
角色 一个类可以扮演多个角色
边界 上层依赖抽象,下层提供实现
多态入口 同一段代码可以替换不同实现

“隔离变化"是接口带来的结果,而不是接口的全部意义


1.2 接口的核心价值

接口 = 抽象"角色/能力"的工具
隔离变化 = 使用接口之后自然带来的收益

2. 隐式接口实现

2.1 定义

隐式实现是最常见的接口实现方式。接口成员直接作为类的公共成员暴露,既可以通过类实例调用,也可以通过接口引用调用。

2.2 写法

public interface ILogger
{
    void Log(string message);
}

public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}

2.3 特点

必须是 public

// 正确
public void Log(string message) { }

// 错误:隐式实现不能是 private/protected
private void Log(string message) { }

既可以通过类调用,也可以通过接口调用

// 通过类实例调用
var logger = new ConsoleLogger();
logger.Log("hello");

// 通过接口引用调用
ILogger iLogger = new ConsoleLogger();
iLogger.Log("world");

2.4 适合的场景

  • 接口方法本来就是类的自然能力(主要职责)
  • 你希望调用方直接从类实例上看到它
  • 接口就是这个类的主要身份之一

典型例子:

// UserRepository 实现 IUserRepository
public class UserRepository : IUserRepository { }

// EmailSender 实现 IMessageSender
public class EmailSender : IMessageSender { }

// FileLogger 实现 ILogger
public class FileLogger : ILogger { }

3. 显式接口实现

3.1 定义

显式实现不将接口成员暴露为类的公共 API,只有在将对象转型为对应接口时,该成员才可见。

3.2 写法

public interface ILogger
{
    void Log(string message);
}

public class ConsoleLogger : ILogger
{
    // 显式实现:不写访问修饰符,加上接口名前缀
    void ILogger.Log(string message)
    {
        Console.WriteLine(message);
    }
}

注意: 显式实现不能加访问修饰符(publicprivate 等),写了会编译报错。

// 错误写法
public void ILogger.Log(string message) { } // 编译错误

3.3 特点

不能直接通过类实例调用

var logger = new ConsoleLogger();
logger.Log("hello");         // 编译错误,不可见

// 必须转型为接口后才能调用
ILogger iLogger = logger;
iLogger.Log("hello");        // 正确

((ILogger)logger).Log("hello"); // 也正确

成员只在"接口视角"下可见

显式实现不是说这个方法不存在,而是说:这个方法只在"你把对象看作某个接口"时才出现。

3.4 适合的场景

场景一:避免污染类的公共 API

public interface IInternalAudit
{
    void Audit();
}

public class OrderService : IInternalAudit
{
    // 主职责:公开暴露
    public void CreateOrder()
    {
        Console.WriteLine("Create order");
    }

    // 辅助契约:隐藏在类 API 之外
    void IInternalAudit.Audit()
    {
        Console.WriteLine("Audit order service");
    }
}
var service = new OrderService();
service.CreateOrder();      // 可见
// service.Audit();         // 不可见

((IInternalAudit)service).Audit(); // 可见

场景二:多个接口成员同名但语义不同

public interface IReader
{
    void Open();
}

public interface IWriter
{
    void Open();
}

public class FileDevice : IReader, IWriter
{
    void IReader.Open()
    {
        Console.WriteLine("Open for reading");
    }

    void IWriter.Open()
    {
        Console.WriteLine("Open for writing");
    }
}
var device = new FileDevice();
((IReader)device).Open(); // Open for reading
((IWriter)device).Open(); // Open for writing

如果用隐式实现,只能有一个 public Open(),两个接口共享同一个实现,当语义不同时这是错误的。


场景三:兼容旧接口 / 辅助接口

.NET BCL 中大量使用这种手法:

public class MyCollection<T> : IEnumerable<T>, IEnumerable
{
    // 主 API:泛型版本,公开暴露
    public IEnumerator<T> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    // 兼容旧接口:显式实现,不污染现代 API
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

场景四:IDisposable 的显式实现

IDisposable 不是类的主要能力,而只是辅助资源管理时:

public class DataExporter : IDisposable
{
    private readonly FileStream _stream;
    private bool _disposed;

    public DataExporter(string filePath)
    {
        _stream = new FileStream(filePath, FileMode.Create);
    }

    // 主 API,清晰暴露
    public async Task ExportAsync(IEnumerable<Order> orders)
    {
        if (_disposed)
            throw new ObjectDisposedException(nameof(DataExporter));

        foreach (var order in orders)
        {
            var bytes = Encoding.UTF8.GetBytes(order.ToString() + "\n");
            await _stream.WriteAsync(bytes);
        }
    }

    // 显式实现,不污染主 API
    void IDisposable.Dispose()
    {
        if (_disposed) return;
        _stream.Dispose();
        _disposed = true;
    }
}
// using 语句能正确触发显式实现的 Dispose()
using var exporter = new DataExporter("output.txt");
await exporter.ExportAsync(orders);
// 块结束时自动调用 IDisposable.Dispose()

3.5 一个高级用法(慎用)

同一个接口,同时存在隐式和显式实现:

public interface IMessage
{
    string GetText();
}

public class Notice : IMessage
{
    // 隐式实现
    public string GetText() => "public text";

    // 显式实现
    string IMessage.GetText() => "interface text";
}
var notice = new Notice();
Console.WriteLine(notice.GetText());          // public text

IMessage msg = notice;
Console.WriteLine(msg.GetText());             // interface text

建议:能不用就别用。 这会让行为分裂,增加理解成本。除非你非常明确地要区分"类视角"和"接口视角"的行为,否则尽量避免。


4. 隐式 vs 显式:如何选择

4.1 核心区分

隐式实现:"这是我的公开能力。"
显式实现:"这是我在扮演某个接口角色时才有的能力。"

4.2 选择标准

优先使用隐式实现,如果:

  • 这个方法本来就是类的自然能力(主职责)
  • 你希望调用方直接从类实例上看到它
  • 接口就是这个类的主要身份之一

使用显式实现,如果:

  • 不想将某个接口成员暴露到类的公共 API
  • 多个接口成员重名,但语义不同(强信号)
  • 这是兼容性接口、框架辅助接口、低优先级接口
  • 想明确区分"类本身职责"和"接口角色职责”
  • 想缩小类对外暴露的表面积,减少误用

4.3 对比速查表

特性 隐式实现 显式实现
访问修饰符 必须 public 不能有访问修饰符
通过类实例调用 可以 不可以
通过接口引用调用 可以 可以
暴露在类公共 API
适合场景 主职责、主能力 辅助契约、冲突解决

5. 接口 vs 抽象类

5.1 核心区别

接口:描述"能做什么"  → 角色 / 能力 / 契约
抽象类:描述"是什么,并且已经有一部分共性实现"  → 基类 / 模板 / 骨架

5.2 多角色 vs 单继承

C# 类只能继承一个基类,但可以实现多个接口:

// 一个类扮演多个角色
public class FileCache : ICache, IDisposable, IAsyncDisposable
{
}

接口的天然优势:一个类可以扮演多个角色,而抽象类做不到。

5.3 什么时候用接口

只需要约束,不需要共享实现:

public interface ISerializer
{
    string Serialize<T>(T obj);
}

// 三种实现完全不同,接口非常合适
public class JsonSerializer : ISerializer { }
public class XmlSerializer : ISerializer { }
public class YamlSerializer : ISerializer { }

5.4 什么时候用抽象类

需要共享实现、共享状态、模板流程:

public abstract class FileImporter
{
    // 共享流程(模板方法)
    public void Import(string path)
    {
        ValidatePath(path);
        var content = File.ReadAllText(path);
        Parse(content);
        Save();
    }

    // 共性实现
    protected virtual void ValidatePath(string path)
    {
        if (string.IsNullOrWhiteSpace(path))
            throw new ArgumentException(nameof(path));
    }

    // 子类各自实现
    protected abstract void Parse(string content);
    protected abstract void Save();
}

需要共享状态:

public abstract class WorkerBase
{
    // 共享字段和构造函数
    protected readonly ILogger _logger;

    protected WorkerBase(ILogger logger)
    {
        _logger = logger;
    }
}

5.5 接口 + 抽象类组合使用(成熟系统的常见做法)

// 对外:依赖接口(稳定契约)
public interface IMessageSender
{
    Task SendAsync(string message);
}

// 对内:抽象类沉淀共性(模板流程 + 共享状态)
public abstract class MessageSenderBase : IMessageSender
{
    protected readonly ILogger _logger;

    protected MessageSenderBase(ILogger logger)
    {
        _logger = logger;
    }

    public async Task SendAsync(string message)
    {
        Validate(message);
        await SendCoreAsync(message);
        _logger.LogInformation("message sent");
    }

    protected virtual void Validate(string message)
    {
        if (string.IsNullOrWhiteSpace(message))
            throw new ArgumentException(nameof(message));
    }

    protected abstract Task SendCoreAsync(string message);
}

// 具体实现:只填差异部分
public class EmailSender : MessageSenderBase
{
    public EmailSender(ILogger<EmailSender> logger) : base(logger) { }

    protected override Task SendCoreAsync(string message)
    {
        Console.WriteLine($"Email: {message}");
        return Task.CompletedTask;
    }
}

设计原则:

  • 接口负责抽象边界
  • 抽象类负责沉淀共性

5.6 选择口诀

优先用接口,如果你在表达:

  • 某种能力 / 某种边界 / 某种可替换实现
  • 某种策略 / 某种外部依赖

优先用抽象类,如果你在表达:

  • 一组对象的共同父类 / 有明确继承关系
  • 有共享实现 / 有共享状态 / 有模板流程

5.7 对比速查表

维度 接口 抽象类
多继承 支持多个 只能单继承
共享实现 不擅长(default 实现慎用) 擅长
共享状态(字段) 不支持 支持
构造函数 没有
适合表达 角色 / 能力 / 契约 家族 / 骨架 / 模板
典型例子 ILogger ICache Stream ControllerBase

6. 什么时候不要定义接口

这部分比"什么时候定义接口"更重要。很多项目的问题,不是接口太少,而是接口太多、太空、太假

6.1 没有替换价值时,不要急着定义接口

// 如果 TaxCalculator 没有多种实现、不是边界、不需要多态
// 直接用具体类即可
public class TaxCalculator
{
    public decimal Calculate(Order order) { ... }
}

// 不必要的接口定义
public interface ITaxCalculator
{
    decimal Calculate(Order order);
}

6.2 纯数据对象不要抽象接口

// 没有意义
public interface IUserDto
{
    string Name { get; set; }
}

// 直接用数据类
public class UserDto
{
    public string Name { get; set; }
}

以下类型通常不需要接口:

  • DTO / VO / ViewModel
  • 领域实体(OrderUserProduct
  • Record 类型
  • Value Object

6.3 稳定的工具类,不需要强行接口化

// 如果没有多种哈希策略切换、没有测试替身需求
// 直接用具体类
public class Md5Hasher
{
    public string Hash(string input) { ... }
}

6.4 应用内部的实现细节类不需要接口

// 只在程序集内部使用,变化可能性低
internal class OrderNumberGenerator
{
    public string Generate() { ... }
}

6.5 不要为了"方便 Mock"而滥造接口

错误顺序:为了 mock → 建接口

正确顺序:设计需要抽象 → 建接口 → 测试顺便受益

天然应该抽象的依赖(测试只是附带收益):

  • 数据库访问
  • 第三方 HTTP 调用
  • 消息队列
  • 缓存
  • 文件系统
  • 时钟、随机数

不需要为测试而强行接口化的类:

  • 纯业务计算类
  • 无外部依赖的领域逻辑

6.6 不要为了"看起来规范"而机械配对接口

// 典型反模式:一比一机械配对,没有抽象价值
public interface IOrderService { ... }
public class OrderService : IOrderService { ... }

public interface IUserManager { ... }
public class UserManager : IUserManager { ... }

public interface IProductHelper { ... }
public class ProductHelper : IProductHelper { ... }

这类代码会导致:

  • 文件数量翻倍
  • 导航成本增加
  • 抽象失真
  • 重构更痛苦

6.7 常见接口滥用模式

反模式 描述 问题
一比一空壳接口 每个类机械配一个 IXXX 无替换价值,纯噪音
胖接口 一个接口包含大量不相关方法 违反接口隔离原则
以类为中心命名 ICommonHelperIManagerIBaseService 抽象不清晰
为 DI 容器而造接口 只是为了注册到容器 本末倒置

7. 什么时候应该定义接口

7.1 五个判断问题

问题一:调用方关心的是"能力"还是"具体实现"?

能不能发消息?能不能写日志?能不能取缓存?
→ 适合接口

问题二:这里是否是系统边界?

仓储 / 外部 API / 支付网关 / 消息队列 / 文件存储 / 缓存 / 时钟
→ 适合接口

问题三:是否存在两种以上"有意义"的实现?

// 有实际多实现价值的接口
public interface ICacheProvider { }
// RedisCacheProvider
// MemoryCacheProvider

public interface INotificationSender { }
// SmsNotificationSender
// EmailNotificationSender
// WechatNotificationSender

问题四:是否需要多态扩展?

// 策略模式:运行时选择不同策略
public interface IDiscountStrategy
{
    decimal Calculate(Order order);
}

问题五:如果没有接口,这段代码是否明显更难演进?

现在不用接口也没问题?没有外部依赖边界?不太可能扩展?
→ 先用具体类,等需求出现再提炼(渐进式抽象)

7.2 应该定义接口的典型场景

// 仓储层(系统边界)
public interface IOrderRepository
{
    Task<Order?> GetByIdAsync(Guid id);
    Task SaveAsync(Order order);
}

// 外部服务(可替换实现)
public interface IPaymentGateway
{
    Task PayAsync(decimal amount);
}

// 策略(多态扩展)
public interface IPricingStrategy
{
    decimal Calculate(Order order);
}

// 小而稳定的环境依赖
public interface IClock
{
    DateTime UtcNow { get; }
}

8. DI 中的接口注册

8.1 核心原则

接口定义在内层,实现放在外层,注册在入口

接口定义在哪个层 → 由哪个层负责,或由组合根统一管理

8.2 典型分层模型

┌───────────────────────────────┐
│         API / Web 层          │  ← 入口、Controller、Middleware
├───────────────────────────────┤
│         Application 层        │  ← 用例、Service、DTO
├───────────────────────────────┤
│          Domain 层            │  ← 实体、领域服务、接口定义
├───────────────────────────────┤
│       Infrastructure 层       │  ← 仓储实现、第三方服务实现
└───────────────────────────────┘

依赖方向:

Domain 层定义:
  IOrderRepository  ← 契约,属于内层

Infrastructure 层实现:
  EfOrderRepository : IOrderRepository  ← 实现,属于外层

Domain 层不知道 EfOrderRepository 的存在,也不应该知道。

8.3 推荐注册方式:各层通过扩展方法自我注册

Infrastructure 层

// Infrastructure/DependencyInjection.cs
public static class InfrastructureServiceCollectionExtensions
{
    public static IServiceCollection AddInfrastructure(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        services.AddDbContext<AppDbContext>(options =>
            options.UseSqlServer(configuration.GetConnectionString("Default")));

        services.AddScoped<IOrderRepository, EfOrderRepository>();
        services.AddScoped<IUserRepository, EfUserRepository>();
        services.AddScoped<IEmailSender, SmtpEmailSender>();
        services.AddScoped<IPaymentGateway, AlipayGateway>();

        return services;
    }
}

Application 层

// Application/DependencyInjection.cs
public static class ApplicationServiceCollectionExtensions
{
    public static IServiceCollection AddApplication(
        this IServiceCollection services)
    {
        services.AddMediatR(cfg =>
            cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));

        services.AddAutoMapper(Assembly.GetExecutingAssembly());

        return services;
    }
}

Program.cs(组合根,保持简洁)

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddApplication()
    .AddInfrastructure(builder.Configuration);

builder.Services.AddControllers();

var app = builder.Build();
app.Run();

8.4 各层注册职责划分

注册内容
Web 层 Controller、Middleware、Swagger、跨层协调配置
Application 层 MediatR、AutoMapper、FluentValidation、应用级 Service
Infrastructure 层 DbContext、仓储实现、第三方客户端、缓存实现、文件存储

8.5 生命周期选择

Singleton    整个进程生命周期内使用同一个实例
Scoped       同一次请求内使用同一个实例
Transient    每次注入都创建一个新实例
类型 推荐生命周期 原因
DbContext Scoped 不能跨请求共享连接和事务
Repository Scoped 依赖 DbContext
Application Service Scoped 通常依赖仓储
HttpClient AddHttpClient 有特殊管理机制
配置类 Singleton 不变的
内存缓存 Singleton 需要全局共享
无状态工具类 Transient 轻量级,无状态

8.6 重要注意事项:Singleton 不能直接依赖 Scoped

// 危险:Scoped 服务被 Singleton 捕获,变成事实上的 Singleton
public class MySingletonService
{
    private readonly IOrderRepository _repo; // Scoped,危险!

    public MySingletonService(IOrderRepository repo)
    {
        _repo = repo;
    }
}

正确做法:使用 IServiceScopeFactory

// 正确:手动创建 Scope
public class MySingletonService
{
    private readonly IServiceScopeFactory _scopeFactory;

    public MySingletonService(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public async Task DoWorkAsync()
    {
        using var scope = _scopeFactory.CreateScope();
        var repo = scope.ServiceProvider
            .GetRequiredService<IOrderRepository>();
        await repo.GetByIdAsync(Guid.NewGuid());
    }
}

9. 真实业务场景判断

场景一:仓储层

判断结论:

项目 结论
要不要接口 要,系统边界
隐式/显式 隐式,仓储方法是主能力
生命周期 Scoped,依赖 DbContext
// Domain 层 - 定义接口
public interface IOrderRepository
{
    Task<Order?> GetByIdAsync(Guid id);
    Task<IReadOnlyList<Order>> GetByUserIdAsync(Guid userId);
    Task SaveAsync(Order order);
    Task DeleteAsync(Guid id);
}

// Infrastructure 层 - 实现
public class EfOrderRepository : IOrderRepository
{
    private readonly AppDbContext _context;

    public EfOrderRepository(AppDbContext context)
    {
        _context = context;
    }

    public async Task<Order?> GetByIdAsync(Guid id)
        => await _context.Orders.FindAsync(id);

    public async Task<IReadOnlyList<Order>> GetByUserIdAsync(Guid userId)
        => await _context.Orders
            .Where(o => o.UserId == userId)
            .ToListAsync();

    public async Task SaveAsync(Order order)
    {
        _context.Orders.Update(order);
        await _context.SaveChangesAsync();
    }

    public async Task DeleteAsync(Guid id)
    {
        var order = await GetByIdAsync(id);
        if (order is not null)
        {
            _context.Orders.Remove(order);
            await _context.SaveChangesAsync();
        }
    }
}

// 注册
services.AddScoped<IOrderRepository, EfOrderRepository>();

场景二:多种实现的通知发送器

判断结论:

项目 结论
要不要接口 要,明确多实现 + 多态切换
隐式/显式 隐式,Send 是主职责
生命周期 Scoped 或 Transient,视实现决定
// 接口定义
public interface INotificationSender
{
    Task SendAsync(string recipient, string message);
    NotificationChannel Channel { get; }
}

public enum NotificationChannel { Email, Sms, Wechat }

// 多种实现
public class EmailNotificationSender : INotificationSender
{
    public NotificationChannel Channel => NotificationChannel.Email;

    public async Task SendAsync(string recipient, string message)
    {
        Console.WriteLine($"[Email] To: {recipient}, Message: {message}");
        await Task.CompletedTask;
    }
}

public class SmsNotificationSender : INotificationSender
{
    public NotificationChannel Channel => NotificationChannel.Sms;

    public async Task SendAsync(string recipient, string message)
    {
        Console.WriteLine($"[SMS] To: {recipient}, Message: {message}");
        await Task.CompletedTask;
    }
}

工厂模式封装选择逻辑(推荐):

public interface INotificationSenderFactory
{
    INotificationSender GetSender(NotificationChannel channel);
}

public class NotificationSenderFactory : INotificationSenderFactory
{
    private readonly IEnumerable<INotificationSender> _senders;

    public NotificationSenderFactory(IEnumerable<INotificationSender> senders)
    {
        _senders = senders;
    }

    public INotificationSender GetSender(NotificationChannel channel)
    {
        return _senders.FirstOrDefault(s => s.Channel == channel)
            ?? throw new InvalidOperationException(
                $"No sender registered for channel: {channel}");
    }
}

// 注册
services.AddScoped<INotificationSender, EmailNotificationSender>();
services.AddScoped<INotificationSender, SmsNotificationSender>();
services.AddScoped<INotificationSender, WechatNotificationSender>();
services.AddScoped<INotificationSenderFactory, NotificationSenderFactory>();

扩展性: 新增 WechatNotificationSender 只需注册,工厂和上层代码完全不变。这就是"对扩展开放,对修改关闭"。


场景三:内部计算器(先不要接口)

判断结论:

项目 结论
要不要接口 先不要,纯内部业务计算
隐式/显式 不适用
生命周期 Transient 或直接 new
// 直接用具体类,不套接口
public class OrderPriceCalculator
{
    public decimal Calculate(Order order)
    {
        var basePrice = order.Items.Sum(i => i.Price * i.Quantity);
        var discount = GetDiscount(order);
        var tax = GetTax(basePrice - discount);
        return basePrice - discount + tax;
    }

    private decimal GetDiscount(Order order)
        => order.IsVip
            ? order.Items.Sum(i => i.Price * i.Quantity) * 0.1m
            : 0;

    private decimal GetTax(decimal amount)
        => amount * 0.13m;
}

何时才提炼接口(渐进式抽象):

// 当出现以下情况时再提炼:
// 1. 需要支持多种计算策略(普通/VIP/活动价)
// 2. 需要在测试中 Mock 价格结果
// 3. 被跨模块共享,需要稳定契约
public interface IPriceCalculator
{
    decimal Calculate(Order order);
}

场景四:同时实现两个接口,名称冲突

判断结论:

项目 结论
要不要接口
隐式/显式 必须显式,两个 Open() 语义不同
public interface IReadable
{
    void Open();
    string Read();
}

public interface IWritable
{
    void Open();
    void Write(string data);
}

public class DataService : IReadable, IWritable
{
    private bool _readMode;
    private bool _writeMode;

    // 显式实现,区分两个 Open 的不同语义
    void IReadable.Open()
    {
        _readMode = true;
        Console.WriteLine("Opened for reading");
    }

    void IWritable.Open()
    {
        _writeMode = true;
        Console.WriteLine("Opened for writing");
    }

    public string Read()
    {
        if (!_readMode)
            throw new InvalidOperationException("Not opened for reading");
        return "data";
    }

    public void Write(string data)
    {
        if (!_writeMode)
            throw new InvalidOperationException("Not opened for writing");
        Console.WriteLine($"Writing: {data}");
    }
}
var service = new DataService();
((IReadable)service).Open();  // Opened for reading
((IWritable)service).Open();  // Opened for writing

场景五:接口分层设计综合示例

// Domain 层 - 定义接口契约
public interface IPaymentGateway
{
    Task<PaymentResult> PayAsync(PaymentRequest request);
}

// Infrastructure 层 - 多个实现
public class AlipayGateway : IPaymentGateway
{
    public async Task<PaymentResult> PayAsync(PaymentRequest request)
    {
        // 调用支付宝 API
        return new PaymentResult { Success = true };
    }
}

public class WechatPayGateway : IPaymentGateway
{
    public async Task<PaymentResult> PayAsync(PaymentRequest request)
    {
        // 调用微信支付 API
        return new PaymentResult { Success = true };
    }
}

// Application 层 - 只依赖接口,不关心实现
public class OrderService
{
    private readonly IOrderRepository _orderRepo;
    private readonly IPaymentGateway _paymentGateway;
    private readonly INotificationSenderFactory _senderFactory;

    public OrderService(
        IOrderRepository orderRepo,
        IPaymentGateway paymentGateway,
        INotificationSenderFactory senderFactory)
    {
        _orderRepo = orderRepo;
        _paymentGateway = paymentGateway;
        _senderFactory = senderFactory;
    }

    public async Task CreateOrderAsync(CreateOrderRequest request)
    {
        var order = new Order(request.UserId, request.Items);

        var paymentResult = await _paymentGateway.PayAsync(
            new PaymentRequest { Amount = order.TotalAmount });

        if (!paymentResult.Success)
            throw new PaymentFailedException();

        await _orderRepo.SaveAsync(order);

        var sender = _senderFactory.GetSender(NotificationChannel.Email);
        await sender.SendAsync(request.UserEmail, "订单创建成功");
    }
}

10. 接口设计原则

10.1 接口要小,要聚焦(接口隔离原则)

// 胖接口:违反接口隔离原则
public interface IUserService
{
    void Create();
    void Delete();
    void Update();
    void Login();
    void Logout();
    void SendEmail();
    void ExportExcel();
}

// 按角色拆分
public interface IUserRepository   { void Create(); void Delete(); void Update(); }
public interface IAuthService      { void Login(); void Logout(); }
public interface IEmailSender      { void SendEmail(); }
public interface IReportExporter   { void ExportExcel(); }

10.2 按"角色"命名,不要按"类"命名

// 好的命名(角色/能力)
public interface IMessageSender { }
public interface IOrderRepository { }
public interface ICacheProvider { }
public interface IClock { }

// 一般的命名(不够清晰)
public interface IUserService { }
public interface ICommonHelper { }
public interface IManager { }

10.3 接口一旦公开,修改成本很高

改一个接口成员会影响所有实现类。

  • 不要轻易把接口做得很大
  • 不要过早承诺过多成员
  • 尽量保持稳定、精简

10.4 依赖倒置 ≠ 到处接口化

正确理解:高层模块依赖稳定抽象,而不是依赖易变细节
错误理解:项目里每个类都必须有一个 IXXX

10.5 渐进式抽象

先用具体类,等到变化出现,或者边界清晰了,再提炼接口。

这叫 YAGNI(You Aren’t Gonna Need It)


11. 完整决策地图

11.1 要不要定义接口?

需要定义接口吗?
├── 是系统边界?(数据库、外部 API、文件系统、消息队列)
│   └── 是 → 定义接口
├── 有多个实现,或可预见替换?
│   └── 是 → 定义接口
├── 需要多态 / 策略 / 插件机制?
│   └── 是 → 定义接口
├── 纯内部实现,无边界,无替换,无多态?
│   └── 先用具体类,等需求出现再提炼(渐进式抽象)
└── 只是为了看起来规范 / 方便 Mock?
    └── 不要

11.2 用隐式还是显式实现?

用隐式实现还是显式实现?
├── 这个方法是类的主要能力 / 主职责?
│   └── 隐式实现
├── 只是为了履行辅助契约,不想污染主 API?
│   └── 显式实现
├── 多个接口同名但语义不同?
│   └── 必须显式实现
└── 兼容旧接口 / 辅助接口(如非泛型 IEnumerable)?
    └── 显式实现

11.3 注册在哪里?

注册在哪里?
├── 仓储、数据库、外部服务实现
│   └── Infrastructure 层注册
├── 应用层协调类(MediatR、AutoMapper 等)
│   └── Application 层注册
├── Web 相关(Controller、Middleware)
│   └── Web 层注册
└── Program.cs 只做组合
    └── 调用各层的注册扩展方法,保持简洁

11.4 接口 vs 抽象类?

接口 vs 抽象类?
├── 表达"角色/能力",可能多个角色叠加?
│   └── 接口
├── 有共享实现、共享状态、模板流程,是"对象家族"?
│   └── 抽象类
└── 两者都需要?
    └── 接口 + 抽象类组合使用
        └── 接口对外暴露契约
            抽象类对内沉淀共性

11.5 生命周期选择?

生命周期?
├── 依赖 DbContext,或需要在同一请求内共享?
│   └── Scoped
├── 全局共享状态,整个进程只需一个?
│   └── Singleton
│       └── 注意:不能直接依赖 Scoped,用 IServiceScopeFactory
└── 无状态,轻量级,每次用新的?
    └── Transient

附录:核心结论速查

接口适合

  • 边界 / 契约 / 策略 / 多态 / 角色 / 可替换依赖

抽象类适合

  • 共性代码 / 模板流程 / 共享状态 / 继承骨架 / 对象家族

不要定义接口的典型情况

  • 没有变化点
  • 没有边界意义
  • 没有多实现需求
  • 只是为了"规范"或"方便 Mock"
  • 只是为了给类配一个 IXXX

隐式实现

  • 这是类的主要能力
  • 希望从类实例直接访问
  • 接口是类的主要身份

显式实现

  • 辅助契约,不想污染主 API
  • 多接口成员重名,语义不同
  • 兼容旧接口、辅助接口
  • 控制暴露面,减少误用