C# 单一职责原则SRP C#如何定义一个类的单一职责

来源:这里教程网 时间:2026-02-21 17:40:41 作者:

怎么判断一个 C# 类是否违反了单一职责原则

最直接的信号是:你给这个类起名字时,不得不用“和”“或”“管理器”“处理器”这类连接词——比如

OrderPaymentEmailNotifier
UserAuthManager
FileLoggerAndEncryptor
。这类名字暴露了它在干多件事。

另一个更实际的判断方式是看修改动机:如果因为「订单退款逻辑变更」和「邮件模板调整」都要改同一个类,那它大概率职责过重。

类中存在多个
public
方法,分别操作完全不相关的领域对象(如同时处理
User
InventoryItem
类依赖了多个高层抽象(如同时注入
IEmailService
IPaymentGateway
ILogger
),且这些依赖之间无业务耦合
单元测试用例明显分属不同场景(例如一部分测“密码重置”,另一部分测“导出 Excel”)

用 C# 接口拆分来落实单一职责

SRP 不是靠“少写点代码”实现的,而是靠明确契约边界。C# 的接口(

interface
)是最轻量、最有效的职责声明工具。

别写一个大而全的

IOrderService
,按行为切分:

public interface IOrderPlacer
{
    Task<Order> PlaceAsync(OrderRequest request);
}
<p>public interface IOrderRefunder
{
Task RefundAsync(Order order, decimal amount);
}</p><p>public interface IOrderNotifier
{
Task NotifyAsync(Order order, NotificationType type);
}

每个接口只回答一个问题:“它能被用来做什么?”而不是“它属于哪个模块?”

实现类可以一对一实现单个接口(如
SqlOrderPlacer
只实现
IOrderPlacer
需要组合能力时,通过构造函数注入多个小接口,而非继承或聚合大接口 避免让一个类实现超过两个业务语义无关的接口(比如
IOrderPlacer
+
IReportExporter
就越界了)

构造函数参数过多往往是 SRP 被破坏的征兆

如果你的类构造函数要传 5 个服务(

IUserRepository
IMailSender
ISmsClient
IEventBus
ICacheProvider
),基本可以确定它在扮演协调者而非执行者。

这时该做的不是加个

Facade
包一层,而是问:这些依赖里,哪些是真正参与“这个类的核心行为”的?其余的,应该由调用方或更高层编排。

提取共用依赖到基类或工厂,仅限技术性基础设施(如日志、配置),不包括业务服务 把非核心协作行为转为事件驱动(如
OrderPlacedEvent
IOrderPlacer
发出,由独立的
EmailOnOrderPlacedHandler
响应)
警惕“为了测试方便”而把所有依赖塞进构造函数——可测试性不该以违背 SRP 为代价

静态工具类和
Helper
后缀是 SRP 的高危区

StringHelper
DateUtility
JsonConverterHelper
这类命名,几乎等于承认“我不知道它到底属于哪一层”。它们往往混着格式化、验证、序列化、加密等逻辑,且随项目增长不断膨胀。

正确做法是按上下文归位:

字符串格式化逻辑 → 归入视图模型或 DTO 的
ToString()
/ 自定义
ToStringBuilder
日期范围校验 → 放进
BookingRequest
IsValid()
或专用
IDateRangeValidator
JSON 序列化封装 → 交给
System.Text.Json.JsonSerializerOptions
配置,或封装为
IJsonSerializer<t></t>
实现

没有“通用逻辑”,只有“当前场景下刚好复用的逻辑”。一旦发现某个

Helper
方法只被一个类调用,就该立刻迁移过去——哪怕只是剪切粘贴。

相关推荐