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

异常处理代码是否违反开闭原则?求重构方案

重构违反开闭原则的异常处理代码

嘿,我完全懂你的困扰!当所有异常都继承自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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:39:09