【问题标题】:Repository pattern - Why exactly do we need Interfaces?存储库模式 - 为什么我们需要接口?
【发布时间】:2012-05-23 20:57:56
【问题描述】:

我从互联网上读到了这一点,上面写着 Interfaces is used for this

  • 使用 TDD 方法
  • 替换持久化引擎

但到目前为止,我无法理解界面将如何发挥作用Replace persistance engine。 让我们考虑一下我正在为EmployeeRepository 创建一个基本(没有泛型)存储库

public class EmployeeRepository
{
  public employee[] GetAll()
  {
     //here I'll return from dbContext or ObjectContex class
  }
}

那么接口是如何形成的呢?

如果假设我创建了一个接口,为什么要使用向上转换?例如

 IEmployee emp = new EmployeeRepository() ;
 vs
 EmployeeRepository emp = new EmployeeRepository();

请准确解释一下接口在存储库模式方面的其他用途。

【问题讨论】:

    标签: c# asp.net-mvc-3 repository-pattern


    【解决方案1】:

    那么接口是如何形成的呢?

    像这样:

    public interface IEmployeeRepository
    {
        Employee[] GetAll();
    }
    

    然后您可以拥有任意数量的实现:

    public class EmployeeRepositoryEF: IEmployeeRepository
    {
        public Employee[] GetAll()
        {
            //here you will return employees after querying your EF DbContext
        }
    }
    
    public class EmployeeRepositoryXML: IEmployeeRepository
    {
        public Employee[] GetAll()
        {
            //here you will return employees after querying an XML file
        }
    }
    
    public class EmployeeRepositoryWCF: IEmployeeRepository
    {
        public Employee[] GetAll()
        {
            //here you will return employees after querying some remote WCF service
        }
    }
    
    and so on ... you could have as many implementation as you like
    

    如您所见,我们如何实现存储库并不重要。重要的是所有存储库和实现都尊重定义的合同(接口),并且都拥有一个返回员工列表的GetAll 方法。

    然后你就会有一个使用这个接口的控制器。

    public class EmployeesController: Controller
    {
        private readonly IEmployeeRepository _repository;
        public EmployeesController(IEmployeeRepository repository)
        {
            _repository = repository;
        }
    
        public ActionResult Index()
        {
            var employees = _repository.GetAll();
            return View(employees);
        }   
    }
    

    了解控制器如何不再依赖于存储库的特定实现?它只需要知道这个实现尊重合同。现在你需要做的就是配置你最喜欢的依赖注入框架来使用你想要的实现。

    这是一个如何使用 Ninject 完成此操作的示例:

    1. 安装Ninject.MVC3 NuGet
    2. 在生成的~/App_Start/NinjectWebCommon.cs 代码中,您只需决定使用带有一行代码的 EF 实现:

      private static void RegisterServices(IKernel kernel)
      {
          kernel.Bind<IEmployeeRepository>().To<EmployeeRepositoryEF>();
      }        
      

    这样,您不再需要对这些存储库类进行任何手动实例化,也无需担心向上转换或其他任何事情。它是为您管理它们的依赖注入框架,并将负责将定义的实现注入到控制器构造函数中。

    只需修改此配置,您就可以切换数据访问技术,而无需触及控制器中的任何一行代码。这就是孤立的单元测试也发挥作用的方式。由于您的控制器代码现在与存储库弱耦合(感谢我们引入的接口),您在单元测试中需要做的就是在存储库上提供一些模拟实现,允许您定义其行为。这使您可以对 Index 控制器操作进行单元测试,而不依赖于数据库或其他任何东西。完全隔离。

    我还邀请您查看following articles 关于 ASP.NET MVC 中的 TDD 和 DI。

    【讨论】:

    • 精彩的答案,每个解释都值得..现在我明白事情是如何运作的..谢谢,我不能将此答案标记为已接受,因为我的积分低于 15,只要我赚到'会接受这个作为答案。
    • 感谢那篇文章.. 假设如果我在我的依赖注入框架中配置 EmployeeRepositoryEF,我的控制器将使用这个 EmployeeRepositoryEF,但是如果我想在同一个控制器中使用 2 个实现怎么办..如果这个问题很愚蠢,我很抱歉..
    • 在我提供的示例中,控制器期望其构造函数中有一个IEmployeeRepository 实例。只能通过一个实现。另一方面,您可能有另一个控制器,它可能需要不同的接口实现。这是完全可能的。您只需要配置您的 DI 框架,使其在 ControllerA 中注入 ImplementationA,在 ControllerB 中注入 ImplementationB。当然,不同的 DI 框架之间的语法会有所不同。
    • 谢谢老兄,现在我明白了。
    • 我完全同意这个概念,但假设我的项目中有 1000 个存储库。如果我创建 1000 个接口,我认为代码有异味。应该是一种保留 DI 但避免有这么多接口的方法。
    【解决方案2】:

    您可以将您的存储库公开为一个接口:

    public interface IEmployeeRepository
    {
        List<Employee> GetAll();
    }
    

    这将允许您拥有许多不同的接口实现,例如默认的:

    public class EmployeeRepository : IEmployeeRepository
    {
        public List<Employee> GetAll()
        {
            // Return from db.
        }
    }
    

    或测试:

    public class TestEmployeeRepository : IEmployeeRepository
    {
        public List<Employee> GetAll()
        {
            // Stub some dummy data.
        }
    }
    

    您使用存储库的代码只对使用接口感兴趣:

    IEmployeeRepository myRepo = MyRepositoryFactory.Get<IEmployeeRepository>();
    

    秘诀是工厂,或将接口解析为可用类型的另一种机制(依赖注入框架,如 Ninject 或 Castle Windsor 将扮演此角色)。

    重点是,消费代码不关心实现,只关心合约(接口)。这使您可以非常轻松地更换实现以进行测试,并促进松散耦合。

    澄清一下,接口的使用和存储库模式之间没有具体的联系,它只是另一种可以利用它们的模式。

    【讨论】:

    • 感谢您的快速响应...还有一个问题,为什么要向上转换 IEmployee emp = new EmployeeRepository() ;EmployeeRepository emp = new EmployeeRepository(); ??
    猜你喜欢
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 2011-04-01
    • 2012-05-15
    • 2011-01-20
    • 2015-04-14
    • 1970-01-01
    • 2021-03-09
    相关资源
    最近更新 更多