异常如何适应抽象易失性依赖关系的松散耦合设计?

时间:2012-03-16 19:12:47

标签: c# exception-handling dependencies

让我们说,不是耦合到实现代码,而是编写抽象并注入抽象实例。例如,您可以使用存储库访问数据库:

abstract class MusicRepository
{
    public abstract IEnumerable<MusicTrack> GetTopFor(DateTime date);
}

class SqlMusicRespository : MusicRepository
{
    public override IEnumerable<MusicTrack> GetTopFor(DateTime date)
    {
        // use some database objects here... maybe they throw an exception
    }
}

class MusicService
{
    private readonly MusicRepository repository;

    public MusicService(MusicRepository repository)
    {
        if (repository == null) throw new ArgumentNullException("repository");
        this.repository = repository;
    }

    public IEnumerable<MusicTopTrack> GetTodaysTop()
    {
        // domain logic that uses the repository to figure out what tracks
        // are in a (new, up, down, or same) position from yesterday
    }
}

我不确定应该从使用代码处理哪些异常类型,或者确切地说应该如何处理异常处理。实际的异常类型随着实现而改变,因此尝试处理任何特定似乎是一种耦合形式。

“异常适配器”是否应该成为定义抽象的一部分......每个实现都捕获任何异常,然后抛出带有捕获异常的适配器作为InnerException?

public override IEnumerable<MusicTrack> GetTopFor(DateTime date)
{
    try
    {
        // use some database objects here... maybe they throw an exception
    }
    catch(Exception exception)
    {
        throw new MusicException(innerException: exception);
    }
}

这似乎是从WebRequest抽象派生的.NET类型的想法。还有替代品吗?在消费代码中为特定类型设置一个catch处理程序是否真的违反了Liskov?

2 个答案:

答案 0 :(得分:2)

我建议接口应该定义一组它们将产生的异常,并定义当任何其他异常转义时,可能会或可能不会对接口实现者的状态做出什么假设。这里至少有三种可能的模式

  1. 要求实现者捕获并重新命名任何不暗示实现者本身存在问题的异常,这样,只要实现者抛出除记录异常之外的异常,就可以假设实现者本身是腐败,或
  2. 要求实现者允许从它所使用的接口对象抛出的异常通过,除非它们破坏了实现者本身,在这种情况下它们应该被捕获并重新命名。
  3. 要求实现者捕获并重新命名每个异常,作为一种类型,指示请求无法完成但实现者可能没有损坏,这种类型表明实现者已损坏,但CPU可能没有着火,或者是表示CPU着火的类型。

不幸的是,微软现有的接口如IEnumerator<T>没有记录任何这样的约定,这意味着没有定义类似于可枚举集合的东西可以有意义地表明存在阻止枚举的条件但并不意味着CPU是着火(InvalidOperationException最有可能导致大多数IEnumerable<T>消费者的正确行为,除了文档指明它意味着集合被修改了。

答案 1 :(得分:1)

Eric Lippert撰写了一篇关于如何处理异常的优秀文章 - 建议您先阅读此内容:

http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx