我目前正在移植一个大型的WIN应用程序,这样我就可以将它扩展到MVC。其中一个原则是它支持多个DAL,这些DAL由fluent config动态绑定。例如
interface IDataStore
{
}
class SqlRepository :IDataStore
{
//do sql stuff
}
class VistaDBRepository : IDataStore
{
//do vista db stuff
}最后
class WCFClientRepository :IDataStore
{
//do WFC stuff
}要在业务逻辑中使用此实现,应如下所示
public IDataStore GetCurrentStore()
{
get
{
switch(EnterpriseConfiguration.Instance.ActiveProvider)
{
case "MSSQL":
return new SqlRepository();
case "VistaDB":
return new VistaDBRespository();
case "WFC":
return new WCFReposity();
}
}
}
void GetDate()
{
using(IDataStore db = GetCurrentStore())
{
//do something with db
}
}由此,存在将在主db提供商(SQL)和移动提供商(WCF)之间监视的连接监听器。如果主提供商的连接断开或不可用,则它将选择次提供商(WFC)作为主提供商。这是为了支持移动用户,效果很好,在局域网内的用户直接转到SQL,当他们离开时会自动切换到WFC。
最后,VistaDB被用作持久性存储(插件信息、表单元数据、位置、国家等)
我想用IOC容器替换这个模式,我的想法是为每个定义的IDataStore构建一个容器,该容器可以绑定到一个名称,进而获得消费
SQLReposity db = Container.Resolve<IDataStore>().ContainerName("MSSQL");
VistaDBReposity db = Container.Resolve<IDataStore>().ContainerName("VistaDB");
WFCReposity db = Container.Resolve<IDataStore>().ContainerName("WCF");对使用Unity或StructureMap构建容器有什么想法吗?
发布于 2015-03-26 23:22:03
尽管您可以为应用程序创建多个容器,但通常不鼓励这样做。拥有多个容器可能会使您的DI配置复杂化,因为在容器之间共享实例会更加困难,并且容器将更难帮助您验证DI配置。
一般来说,我只会在应用程序由多个独立的模块组成的情况下使用多个容器,这些模块几乎没有任何共享。在所有其他场景中,使用单个全局容器实例。
在查看您的设计时,我的第一个想法是:为什么业务层必须知道有多个IDatabase实现?您的设计似乎通过公开GetCurrentStore()方法来传达这一点。
相反,我会选择让它对业务层透明和隐藏。这样做会使消费代码变得更简单,因为它不必知道有多个商店。这使得API更小,因此更简单。更容易阅读,更容易测试。
使其透明意味着您必须创建一个能够分派和回退到正确实现的IDatabase实现。这意味着这个“dispatcher”可以在内部调用GetCurrentStore(),但可以对应用程序的其余部分隐藏这一点。这也可能是实现重试逻辑的地方,也可能是实现回退行为的地方。
我想象这个实现看起来像这样:
public class DatabaseDispatcher : IDatabase
{
private readonly SQLReposity primaryStore;
private readonly WFCReposity fallbackStore;
private readonly VistaDBReposity persistentStore;
public DatabaseDispatcher(
SQLReposity primaryStore,
WFCReposity fallbackStore,
VistaDBReposity persistentStore) {
this.primaryStore = primaryStore;
this.fallbackStore = fallbackStore;
this.persistentStore = persistentStore;
}
// IDatabase methods here. Example:
public TResult Execute<TResult>(IQuery<TResult> query)
try {
return GetCurrentStore().Execute<TResult>(query);
} catch (Exception ex) {
// Decide what to do here. Is primary store offline? Then fallback
}
}
private IDatabase GetCurrentStore() {
// complex fallback logic here.
}
}这个DatabaseDispatcher实现是可以放在Composition Root中的一块基础设施。您可能有多个具有不同调度策略的应用程序。如您所见,此实现依赖于具体的存储库类型。这使得注册/解析此类型非常容易,如下所示:
// Ninject
kernel.Bind<IDatabase>().To<DatabaseDispatcher>();
// Unity
container.RegisterType<IDatabase, DatabaseDispatcher>();
// Simple Injector
container.Register<IDatabase, DatabaseDispatcher>();让DatabaseDispatcher仅依赖于抽象也是可能的,您可以很容易地做到这一点,而无需使用命名注册:
// Ninject
kernel.Bind<IDatabase>().ToMethod(c => new DatabaseDispatcher(
primaryStore: kernel.Get<SQLReposity>(),
fallbackStore: kernel.Get<WFCReposity>(),
persistentStore: kernel.Get<VistaDBReposity>()));
// Unity
container.Register<IDatabase>(new InjectionFactory(c => new DatabaseDispatcher(
primaryStore: c.Resolve<SQLReposity>(),
fallbackStore: c.Resolve<WFCReposity>(),
persistentStore: c.Resolve<VistaDBReposity>())));
// Simple Injector
container.Register<IDatabase>(() => new DatabaseDispatcher(
primaryStore: container.GetInstance<SQLReposity>(),
fallbackStore: container.GetInstance<WFCReposity>(),
persistentStore: container.GetInstance<VistaDBReposity>()));这允许您拥有一个DI容器实例,这可以极大地降低您的Composition的复杂性。
我注意到的另一件事是,您的IDatabase抽象似乎实现了IDisposable。这使得IDatabase成为leaky abstraction - Dependency Inversion Principle冲突的一种特定形式,因为:
抽象不应该依赖于细节。细节应该取决于抽象。
有任何东西要处理的事实是一个实现细节,消费者不应该知道这一点。所以你应该只在实现上实现IDisposable,如果所有的实现都需要处理的话。让容器管理数据库实例的生命周期,而不是让消费者自行处理。尤其是因为您可能希望在请求期间缓存这些数据库实例,所以您不希望向使用者公开Dispose方法,因为这将允许他们在请求尚未完成时处理该实例,并且该实例仍需要由其他代码使用。
https://stackoverflow.com/questions/29281364
复制相似问题