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

4
这是我的领域模型和通用存储库的设计。
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的要求。

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


4
好的,我会尽力完成你的翻译请求。建议使用IoC/依赖注入框架,并只使用接口。IOC容器将替换接口为正确的实现,而不要使用通用基础存储库。 - Alex Peta
我使用Ninject,但是我不是注入存储库本身,而是尝试注入工厂,该工厂可以实现类似于EFRepositoryFactory、NHibernateRepositoryFactory等。 - Mehmet Ataş
2个回答

3
我同意其他人的观点,认为你会从查看像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
我使用Ninject,但是我不是注入存储库本身,而是尝试注入工厂,该工厂可以实现类似于EFRepositoryFactory、NHibernateRepositoryFactory等。 - Mehmet Ataş

1

你的代码有三个问题:

第一个问题是 IEntity。只有一种类型的 ID 是违反 DDD 的,因为在 DDD 中,对象的标识由领域给出,可以是任何东西,从字符串、整数、GUID 到复杂类型。

第二个问题是使用 IRepository 的通用存储库,这是非常无用的,因为你很少会传递这个接口,而大多数情况下会传递具体实体的存储库接口。

第三个问题是,在 DDD 中,存储库应该仅存在于聚合根中,而这在你的设计中并没有体现。

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


1
DDD不是教条,存储库模式在任何应用程序中都非常有用,只是在处理域时,存储库与聚合根一起工作。但是在同一个应用程序中,您可以拥有其他不服务于域的存储库,因此它们不与AR绑定。关于单个ID类型是否违反DDD,我不知道,这可能对域有效,也可能无效,这不是必须遵守的硬性规定。 - MikeSW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接