假设我有存储库:

interface IRepo
{
    Foo Get(int id);
    void Add(Foo f);
}

现在,要求我只能拥有一个具有给定属性的 Foo ,例如... Id 。显然,如果我用一些 SQL 后端实现 IRepo 并将 Foo.Id 映射到主键,我会免费得到这样的东西 - 我可能会得到它作为某种 PKViolationException 。但这正在变得特定于实现,所以无论我在哪里使用这个 IRepo 实现并开始捕获这些异常,我都会失去松散耦合的好处。

现在,我将如何解决这个问题?我是否应该添加某种服务层来首先检查对象是否存在,然后抛出一些与存储库无关的异常?
class Service
{
    IRepo repo;

    public void AddFoo(Foo foo)
    {
        if(repo.Get(foo.Id) != null)
            repo.Add(foo);
        else
            throw new FooAlreadyExistsException(foo.Id);
    }
}

这个解决方案看起来很糟糕,因为 repo.Add(foo) 仍然可以抛出一些异常,尤其是在检查之后(通过其他事件)添加了具有给定 Id 的对象时,所以我只是向我的基础设施添加了一个额外的调用,但 yield 很少或没有。

似乎我应该小心并考虑到此类异常来实现 IRepo(可以捕获 PKViolationException 并将其转换为示例 SQL 实现中的 FooAlreadyExistsException),但是如何确保每个 IRepo 实现都遵循此类规范?

您一般如何解决这些问题?

最佳答案

“似乎我应该小心并在考虑此类异常的情况下实现 IRepo(可以捕获 PKViolationException 并将其转换为 FooAlreadyExistsException 在示例 SQL 实现中)”

你是对的。抛出的异常成为接口(interface)契约的一部分,实现必须遵守这个契约。编译器不会为您强制执行此操作,因此您必须完全清楚期望。

“但是如何确保每个 IRepo 实现都遵循这样的规范?”

作为接口(interface)编写者,您不能对实现它的类负责。如果其他类有缺陷并暴露了泄漏的抽象,那就是它们的缺陷。您所能做的就是绝对清楚您正在定义的契约(Contract)。

存储库的目的是抽象出数据库的实现细节, 包括其异常 。因此,您还应该抽象出实现特定的异常......

interface IRepo
{
    /// <exception cref="FooAlreadyExistsException">Thrown if the specified Foo object already exists in the database.</exception>
    void Add(Foo f);
}

class ConcreteRepo
{
    public void Add(Foo f)
    {
        try
        {
            // Database stuff...
        }
        catch (PKViolationException e)
        {
            throw new FooAlreadyExistsException(e);
        }
    }
}

关于design-patterns - 存储库模式和集合错误处理,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/13108564/

10-10 08:44