使用接口而不是抽象类来实现存储库模式的优点是什么?
在使用Repository模式时,使用接口(interface)而不是抽象类(abstract class)的优势是什么?这个问题的出现是因为作者个人倾向于将纯粹与业务相关的方法放在接口中,而将受保护的方法放在抽象类中。在作者的设计中,抽象类中的方法对外部世界是不可见的,只有通过接口才能访问到业务模型。
作者使用抽象类是为了避免在不需要的仓储(repository)中实现不需要的方法,例如在某个仓储中没有业务意义的删除数据的方法。而使用接口的优势在于它提供了对业务模型进行操作的答案,以及在单元测试中更容易使用。作者使用的抽象类通常与数据库紧密耦合,只能进行集成测试,无法进行单元测试。如果将业务目的的仓储用抽象类实现,将无法在单元测试中使用它们。
为什么会有这种情况呢?实现(plumbing)可以使用任一方法来隐藏。
所有仓储共享相同的实现(使用相同的数据源),因此所有仓储都继承自同一个抽象类。这个实现只需编写一次。接口则服务于特定的业务目的。只有处理该目的的仓储才实现该接口。通常情况下,并不会有50个数据源,但可能会有50个业务目的。这是我个人的设计方式,我认为这样做很方便、清晰,但像所有事情一样,有多种做法。
所以,如果我理解你的删除示例正确的话,你是说你更喜欢在仓储中使用抽象类而不是接口?
不,我同时使用抽象类和接口,它们具有不同的目的。抽象类用于技术目的(即与数据源的通信),接口用于仓储的业务目的/含义。让我在我的回答中添加一个更具体的示例来解释。
使用接口而不是抽象类的主要优势在于接口的透明性,尤其是当你无法访问你要继承的类的源代码时。接口的透明性使得你可以生成一个已知范围的单元测试:如果你测试一个接受接口作为参数的类(使用依赖注入方法),你知道你正在使用一个已知的数量进行测试;接口的测试实现只包含你的测试代码。
同样,在测试存储库时,你知道你只是在测试存储库中的代码。这有助于限制测试中可能的变量/交互数量。
解决方法是使用接口来定义存储库模式,而不是抽象类。通过使用接口,我们可以实现透明性和单元测试的优势,同时限制测试中可能的变量和交互。
以下是使用接口替代抽象类的示例代码:
public interface IRepository{ T Get(int id); void Add(T entity); void Update(T entity); void Delete(T entity); } public class MySqlRepository : IRepository { // 实现接口中定义的方法 public T Get(int id) { // 获取实体的逻辑 } public void Add(T entity) { // 添加实体的逻辑 } public void Update(T entity) { // 更新实体的逻辑 } public void Delete(T entity) { // 删除实体的逻辑 } } public class UserRepository { private readonly IRepository _repository; public UserRepository(IRepository repository) { _repository = repository; } public User GetUser(int id) { return _repository.Get(id); } public void AddUser(User user) { _repository.Add(user); } public void UpdateUser(User user) { _repository.Update(user); } public void DeleteUser(User user) { _repository.Delete(user); } } public class User { public int Id { get; set; } public string Name { get; set; } // 其他属性和方法 }
通过使用接口,我们可以定义一个通用的存储库模式,并为每个实体类型实现具体的存储库。在存储库的测试中,我们可以使用一个模拟的存储库实现来限制测试的范围。这样,我们可以更容易地编写和维护单元测试,并确保我们只测试存储库中的代码,而不会受到其他变量和交互的干扰。