异常处理代码是否违反开闭原则?求重构方案
重构违反开闭原则的异常处理代码
嘿,我完全懂你的困扰!当所有异常都继承自System.Exception时,确实没法直接套用Shape示例那种基于多态的开闭实现,但咱们有两种非常优雅的重构方案,完美解决这个问题,以后加新异常类型根本不用碰原有代码!
先看问题代码(补全你提供的片段)
原来的switch写法最大的问题是:每加一种新异常,就得修改这个switch块,完全违反了对扩展开放、对修改关闭的原则:
switch (exception) { case DuplicateNameException _: returnMessage = DefaultResponseMessages.RecordExistsError; break; case KeyNotFoundException _: returnMessage = DefaultResponseMessages.RecordNotFoundError; break; // 以后加新异常,就得在这加case,改原有代码 default: returnMessage = DefaultResponseMessages.GenericError; break; }
方案1:策略模式(适合复杂处理场景)
这种方式把每个异常的处理逻辑封装成独立类,新增异常只需要加新类,完全不碰原有逻辑。
第一步:定义异常处理接口
public interface IExceptionHandler { // 判断当前处理器能否处理该异常 bool CanHandle(Exception exception); // 返回对应的错误消息 string GetErrorMessage(Exception exception); }
第二步:为每个异常实现处理器
比如处理DuplicateNameException:
public class DuplicateNameExceptionHandler : IExceptionHandler { public bool CanHandle(Exception exception) { return exception is DuplicateNameException; } public string GetErrorMessage(Exception exception) { // 如果需要,还可以在这里对exception做额外处理 return DefaultResponseMessages.RecordExistsError; } }
再比如处理KeyNotFoundException:
public class KeyNotFoundExceptionHandler : IExceptionHandler { public bool CanHandle(Exception exception) { return exception is KeyNotFoundException; } public string GetErrorMessage(Exception exception) { return DefaultResponseMessages.RecordNotFoundError; } }
第三步:统一调度处理器
把所有处理器注入到服务中(比如用依赖注入框架),然后找到对应处理器处理:
public class ExceptionResponseService { private readonly IEnumerable<IExceptionHandler> _exceptionHandlers; // 构造函数注入所有IExceptionHandler实现 public ExceptionResponseService(IEnumerable<IExceptionHandler> exceptionHandlers) { _exceptionHandlers = exceptionHandlers; } public string GetResponseMessage(Exception exception) { // 找到第一个能处理该异常的处理器 var handler = _exceptionHandlers.FirstOrDefault(h => h.CanHandle(exception)); // 有处理器就用它的消息,否则返回通用错误 return handler?.GetErrorMessage(exception) ?? DefaultResponseMessages.GenericError; } }
为什么符合开闭原则?
- 新增异常类型?只需要新建一个实现
IExceptionHandler的类,注册到DI容器里就行,原有代码一行都不用改 - 原有逻辑完全对修改关闭,对扩展开放
方案2:字典映射(适合简单场景)
如果你的异常处理逻辑只是返回固定消息,不需要额外操作,用字典映射更简洁:
public class ExceptionResponseService { // 提前注册异常类型和对应的消息生成逻辑 private readonly Dictionary<Type, Func<Exception, string>> _exceptionMessageMap = new() { { typeof(DuplicateNameException), _ => DefaultResponseMessages.RecordExistsError }, { typeof(KeyNotFoundException), _ => DefaultResponseMessages.RecordNotFoundError } }; public string GetResponseMessage(Exception exception) { // 尝试获取对应类型的处理逻辑 if (_exceptionMessageMap.TryGetValue(exception.GetType(), out var messageGenerator)) { return messageGenerator(exception); } // 没有匹配就返回通用错误 return DefaultResponseMessages.GenericError; } }
新增异常时只需要加一行:
{ typeof(YourNewException), _ => DefaultResponseMessages.YourNewError }
同样不用修改原有逻辑,完美符合开闭原则!
总结
- 策略模式适合需要对异常做复杂处理(比如日志、额外数据处理)的场景,扩展性极强
- 字典映射适合简单的消息返回场景,代码更简洁
两种方案都彻底解决了原来switch写法违反开闭原则的问题,以后维护起来轻松多了!
内容的提问来源于stack exchange,提问作者Kemal Fahri
相关产品推荐
相关产品推荐

