4

我现在有一些空闲时间,所以我想了解一下 DI 和 IoC 容器。我无缘无故地选择了统一,除了我可以说的主要框架之间没有重大差异,我应该太担心开始。随着事情变得越来越复杂,我意识到我可能需要改变,但现在我希望它能做到。

所以,我正在处理一个相对简单的数据访问场景,并实现了以下接口和数据访问类。

public interface IEventRepository
{
    IEnumerable<Event> GetAll();
}

public class EventRepository : IEventRepository
{
    public IEnumerable<Event> GetAll()
    {
        // Data access code here
    }
}

然后使用我可以执行以下操作。

IUnityContainer container = new UnityContainer();
container.RegisterType(typeof(IEventRepository), typeof(EventRepository));

var eventRepo = container.Resolve<IEventRepository>();
eventRepo.GetAll();

如果我需要根据我的理解在 6 个月内更改我的数据库提供程序,我会创建一个新的 IEventRepository 实现并更新类型注册,那很好。

现在,这就是我感到困惑的地方。例如,如果我想实现一些缓存,我可以从 IEventRepository 的适当实现继承并覆盖适当的方法来实现必要的缓存。但是,这样做会使使用通过 DI 传入的 Moq 实现来测试缓存是否正常工作变得更加困难,因此本着 DI 的真正精神,我认为创建 IEventRepository 的实现,然后使用 DI 请求一个IEventRepository 的实际数据访问实现就像这样。

public class CachedEventRepository : IEventRepository
{
    private readonly IEventRepository _eventRepo;

    public CachedEventRepository(IEventRepository eventRepo)
    {
        if (eventRepo is CachedEventRepository)
            throw new ArgumentException("Cannot pass a CachedEventRepository to a CachedEventRepository");

        _eventRepo = eventRepo;
    }

    public IEnumerable<Event> GetAll()
    {
        // Appropriate caching code ultimately calling _eventRepo.GetAll() if needed
    }
}

这是有道理的还是我做错了?你有什么建议?如果我做得正确,我该如何解决以下情况,以便 CachedEventRepository 获得 IEventRepository 的适当数据访问实现?

IUnityContainer container = new UnityContainer();
container.RegisterType(typeof(IEventRepository), typeof(EventRepository));
container.RegisterType(typeof(IEventRepository), typeof(CachedEventRepository));

var eventRepo = container.Resolve<IEventRepository>();
eventRepo.GetAll();

非常感谢您的帮助。

编辑 1 以下是我希望能够执行的最小起订量测试,我认为使用继承是不可能的,并且需要 DI。

var cacheProvider = new MemoryCaching();

var eventRepo = new Mock<IEventRepository>(MockBehavior.Strict);
eventRepo
    .Setup(x => x.GetAll())
    .Returns(() =>
    {
        return new Event[] { 
            new Event() { Id = 1}, 
            new Event() { Id = 2}
        };
    });

var cachedEventRepo = new CachedEventRepository(
    eventRepo.Object, 
    cacheProvider);

var data = cachedEventRepo.GetAll();
data = cachedEventRepo.GetAll();
data = cachedEventRepo.GetAll();
Assert.IsTrue(data.Count() > 0);
eventRepo.Verify(x => x.GetAll(), Times.Once());

// This set method should expire the cache so next time get all is requested it should
// load from the database again
cachedEventRepo.SomeSetMethod();

data = cachedEventRepo.GetAll();
data = cachedEventRepo.GetAll();
Assert.IsTrue(data.Count() > 0);
eventRepo.Verify(x => x.GetAll(), Times.Exactly(2));
4

3 回答 3

2

好的,经过对这个主题的一些思考和对 Unity 的一些研究,我想出了这个。

public class EventRepository : IEventRepository
{
    private readonly IDbManager _dbManager;

    public EventRepository(IDbManager dbManager)
    {
        _dbManager = dbManager;
    }

    public virtual IEnumerable<Event> GetAll()
    {
        // Data access code
    }
}

public class CachedEventRepository : IEventRepository
{
    private readonly ICacheProvider _cacheProvider;
    private readonly IEventRepository _eventRepo;

    public ICacheProvider CacheProvider
    {
        get { return _cacheProvider; }
    }

    public CachedEventRepository(IEventRepository eventRepo, ICacheProvider cacheProvider)
    {
        if(eventRepo is CachedEventRepository)
            throw new ArgumentException("eventRepo cannot be of type CachedEventRepository", "eventRepo");

        _cacheProvider = cacheProvider;
        _eventRepo = eventRepo;
    }

    public IEnumerable<Event> GetAll()
    {
        // Caching logic for this method with a call to _eventRepo.GetAll() if required
    }
}

这需要以下统一注册。IEventRepository 的解析请求将返回一个 CachedEventRepository。如果我想快速删除缓存,我只需删除 CachedEventRepository 注册,它就会恢复到 EventRepository。

IUnityContainer container = new UnityContainer();
container.RegisterType<IDbManager, SqlDbManager>();
container.RegisterType<ICacheProvider, MemoryCaching>();
container.RegisterType<IEventRepository, EventRepository>();
container.RegisterType<IEventRepository, CachedEventRepository>(
    new InjectionConstructor(
        new ResolvedParameter<EventRepository>(),
        new ResolvedParameter<ICacheProvider>())
    );

这样就可以进行我所追求的测试了。

一个简单的数据访问测试... SQL 是否有效

IUnityContainer container = new UnityContainer();
container.RegisterType<IDbManager, SqlDbManager>();
container.RegisterType<EventRepository>();

var repo = container.Resolve<EventRepository>();

var data = repo.GetAll();

Assert.IsTrue(data.Count() > 0);

一个简单的缓存测试...缓存系统是否工作

var cache = new MemoryCaching();

var getVal = cache.Get<Int32>(
    "TestKey",
    () => { return 2; },
    DateTime.UtcNow.AddMinutes(5));

Assert.AreEqual(2, getVal);

getVal = cache.Get<Int32>(
    "TestKey",
    () => { throw new Exception("This should not be called as the value should be cached"); },
    DateTime.UtcNow.AddMinutes(5));

Assert.AreEqual(2, getVal);

以及对两者一起工作的测试......各个方法的缓存是否按预期工作。缓存是否应该过期,方法参数是否正常工作以触发新的数据库请求等。

var cacheProvider = new MemoryCaching();

var eventRepo = new Mock<IEventRepository>(MockBehavior.Strict);
eventRepo
    .Setup(x => x.GetAll())
    .Returns(() =>
    {
        return new Event[] { 
            new Event() { Id = 1}, 
            new Event() { Id = 2}
        };
    });

var cachedEventRepo = new CachedEventRepository(
    eventRepo.Object,
    cacheProvider);


cachedEventRepo.CacheProvider.Clear();
var data = cachedEventRepo.GetAll();
data = cachedEventRepo.GetAll();
data = cachedEventRepo.GetAll();
Assert.IsTrue(data.Count() > 0);
eventRepo.Verify(x => x.GetAll(), Times.Once());


cachedEventRepo.SomeSetMethodWhichExpiresTheCache();
data = cachedEventRepo.GetAll();
data = cachedEventRepo.GetAll();
Assert.IsTrue(data.Count() > 0);
eventRepo.Verify(x => x.GetAll(), Times.Exactly(2));

你觉得这怎么样?我认为它提供了良好的分离性和良好的可测试性。

于 2012-04-20T10:23:31.847 回答
1

为什么不尝试将所有缓存逻辑封装在一个类中?所以你会得到类似的东西:

public interface ICacheManager {}

public class CacheManager : ICacheManager {}

因此,您可以编写所有单元测试以确保您的缓存逻辑正常。这将是CacheManagerTest课堂!

然后你可以用这种方式改变你的班级:

public class EventRepository : IEventRepository
{
private ICacheManager _cacheManager;
public EventRepository(ICacheManager cacheManager)
{
    _cacheManager = cacheManager;
}
    public IEnumerable<Event> GetAll()
    {
        // Data access code here
    }
}

所以,你不需要在你的EventRepositoryTest类中测试缓存逻辑,因为它已经测试过了。

比你可以设置你的 IoC 容器返回一个 ICacheManager 的实例和一些缓存策略的参数。

更新 好的,最后一次尝试:

public interface IEventRepo
{
    IEnumerable<Event> GetAll();
}

public interface ICacheProvider
{
    bool IsDataCached();
    IEnumerable<Event> GetFromCache();
}

public class CacheProvider : ICacheProvider
{

    public bool IsDataCached()
    {
        //do smth
    }

    public IEnumerable<Event> GetFromCache()
    {
        //get smth
    }
}


public class EventRepo : IEventRepo
{
    private ICacheProvider _cacheProvider;

    public EventRepo(ICacheProvider cacheProvider)
    {
     _cacheProvider = cacheProvider
    }

    public IEnumerable<Event> GetAll()
    {
        if (_cacheProvider.IsDataCached())
        {
            return _cacheProvider.GetFromCache();
        }
        else
        {
            //get from repo, save data in cache etc
        }
    }
}

[TestClass]
public class EventRepoTest
{
    [TestMethod]
    public void GetsDataFromCacheIfDataIsCachedTest()
    {
        var cacheProvider = new Mock<ICacheProvider>(MockBehavior.Strict);
        cacheProvider
            .Setup(x => x.IsDataCached())
            .Returns(() =>
            {
                return true;
            });
        cacheProvider
            .Setup(x => x.GetFromCache())
            .Returns(
            () => {
            return new Event[] { 
                new Event() { Id = 1}, 
                new Event() { Id = 2}
                };
            }
            );
        var eventRepo = new EventRepo(cacheProvider.Object);

        var data = eventRepo.GetAll();
        cacheProvider.Verify(x => x.GetFromCache(), Times.Once());
    }

    [TestMethod]
    public void GetsDataFromDataBaseIfNotCachedTest()
    {
        var cacheProvider = new Mock<ICacheProvider>(MockBehavior.Strict);
        cacheProvider
            .Setup(x => x.IsDataCached())
            .Returns(() =>
            {
                return false;
            });
        cacheProvider
            .Setup(x => x.GetFromCache())
            .Returns(
            () =>
            {
                return new Event[] { 
                new Event() { Id = 1}, 
                new Event() { Id = 2}
                };
            }
            );
        var eventRepo = new EventRepo(cacheProvider.Object);

        var data = eventRepo.GetAll();
        cacheProvider.Verify(x => x.GetFromCache(), Times.Never());
    }
}

不确定 Moq 语法,因为 WinPhone 没有 Moq,但我认为这不是问题。

于 2012-04-19T16:03:30.130 回答
1

我认为您使用 CachedEventRepository 走在正确的轨道上,但我会将 EventRepository 的 GetAll 方法设为虚拟,并拥有 CachedEventRepository 子类 EventRepository。然后子类可以覆盖GetAll,检查缓存,如果没有找到就调用base.GetAll。然后它可以缓存结果并返回列表。

通过这种方式,缓存逻辑与数据访问逻辑是分开的,并且子类正在将缓存行为添加到您的存储库中。

然后,您可以选择是否需要缓存存储库,例如从配置文件中选择并适当地配置 Unity 容器。

此外,您还可以为缓存服务提供一个接口,以便在对 CachedEventRepository 进行单元测试时模拟它。

于 2012-04-19T16:03:56.137 回答