假设我有存储库:
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/