设计模式:抽象工厂和通用存储库

时间:2012-11-17 15:51:12

标签: c# design-patterns domain-driven-design repository-pattern abstract-factory

这是我的域模型和通用存储库的设计

public interface IEntity 
{
     long Id { get; }
}

public interface IRepository<T> where T : class, IEntity, new()
{
     void Save(T entity);
     void Delete(long id);
     T Get(long id);
     IEnumerable<T> GetAll();
}

public interface IUserRepository : IRepository<User> 
{
     User Login(string username, string password);
}

public class User : IEntity
{
     // Implementation of User
}

public abstract class BaseRepository<T> : IRepository<T> where T : class, IEntity, new()
{
      // Implementation of IRepository
}

public class UserRepository : BaseRepository<User>, IUserRepository
{
      // Implementation of IUserRepository
      // Override BaseRepository if required
}

当我想实例化存储库实例时,我使用了一个实现以下接口的工厂

public interface IRepositoryFactory
{
     R CreateRepository<R, T>()
          where R : IRepository<T>
          where T : class, IEntity, new();
}

并使用工厂对象,如下所示

1. IRepositoryFactory factory = CreateFactory();
2. IUserRepository repository = factory.CreateRepository<IUserRepository, User>();
3. User user = repository.Login("user", "1234");

我的问题出在第二行。我想像我一样使用我的工厂。

// Without specifying the User type parameter
factory.CreateRepository<IUserRepository>()

由于我的IRepository接口对实体类型有约束,我的工厂使用相同的约束来满足IRepository的要求。

有没有办法将此参数与客户端隔离?

2 个答案:

答案 0 :(得分:2)

我同意其他人的意见,你会从像Ninject这样的DI / IoC框架中受益。

所以这个答案并不是建议不遵循其他建议。但是,仍有一些方法可以让您在较低级别解决问题。此代码未经过良好测试,但您可以执行以下操作:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using NUnit.Framework;

namespace TestConsole1
{
  public interface IEntity
  {
    long Id { get; }
  }

  public interface IRepository<T> where T : class, IEntity, new()
  {
    void Save(T entity);
    void Delete(long id);
    T Get(long id);
    IEnumerable<T> GetAll();
  }

  public interface IUserRepository : IRepository<User>
  {
    User Login(string username, string password);
  }

  public class User : IEntity
  {

    // Implementation of User
    public long Id
    {
      get { return 42; }
    }
  }

  public abstract class BaseRepository<T> : IRepository<T> where T : class, IEntity, new()
  {
    // Implementation of IRepository
    public void Save(T entity)
    {
      throw new NotImplementedException();
    }

    public void Delete(long id)
    {
      throw new NotImplementedException();
    }

    public T Get(long id)
    {
      throw new NotImplementedException();
    }

    public IEnumerable<T> GetAll()
    {
      throw new NotImplementedException();
    }
  }

  public class UserRepository : BaseRepository<User>, IUserRepository
  {
    // Implementation of IUserRepository
    // Override BaseRepository if required
    public User Login(string username, string password)
    {
      return new User();
    }
  }

  class Factory 
  {
    public T CreateRepository<T>() where T : class 
    {
      //TODO: Implement some caching to avoid overhead of repeated reflection
      var abstractType = typeof(T);
      var types = AppDomain.CurrentDomain.GetAssemblies().ToList()
          .SelectMany(s => s.GetTypes())
          .Where(p => p.IsClass && 
                      !p.IsAbstract && 
                      abstractType.IsAssignableFrom(p));

      var concreteType = types.FirstOrDefault();
      if (concreteType == null)
        throw new InvalidOperationException(String.Format("No implementation of {0} was found", abstractType));

      return Activator.CreateInstance(concreteType) as T;
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      var factory = new Factory();
      var userRepo = factory.CreateRepository<IUserRepository>();
      Console.WriteLine(userRepo.GetType());
      User user = userRepo.Login("name", "pwd");
      Console.WriteLine(user.Id);
      Console.ReadKey();
    }
  }
}

正如此代码所揭示的,一个中心点是您需要处理接口和具体类之间的耦合,例如在IUserRepository和UserRepository之间。如果您不通过映射器或类似方法直接处理此关系,则可以实现更自动的方式,如代码中所示的方式。

但是,如果您使用类似Ninject之类的东西来为您处理此问题,那么您将更好地投入时间,因为您很可能会发现工厂类的复杂性会随着时间的推移而显着增加。

溴。的Morten

答案 1 :(得分:1)

您的代码有3个问题:

首先是IEntity。具有单一类型的ID是针对DDD的,因为在DDD中,对象的标识由域给出,它可以是从string,int,guid到复杂类型的任何东西。

其次是具有IRepository的通用存储库,这也是非常无用的,因为您很少会传递此接口,并且通常会为具体实体的存储库传递接口。

第三件事是DDD存储库只应存在于聚合根中,这不会反映在您的设计中。

如果你解决这个问题,你会发现DI框架可以很容易地实现特定实体的存储库接口的实现。