如何使用简单的注入器将其接口注册为单例类型?

时间:2015-04-08 12:52:55

标签: c# dependency-injection simple-injector

我有以下模型:

public interface IUserLookupService {
    Guid[] GetDirectChilds(Guid userId);
    Guid[] GetAllChilds(Guid userId);
    Guid GetDepartment(Guid userId);
}

public interface ICanRefreshCache{
    void Refresh();
}

public class XHandler : IEventHandler<UserNameChanged> { ... }
public class YHandler : IEventHandler<TeamCreated> { ... }

public class CachedUserLookupService
    : IUserLookupService,
    ICanRefreshCache,
    IEventHandler<UserNameChanged>
{
    private Func<ISession> _sessionFactory;
    private IDictionary<Guid, UserInfo> _users = new Dictionary<Guid, UserInfo>();

    public CachedUserLookupService(Func<ISession> sessionFactory) {
        _sessionFactory = sessionFactory;
    }

    public void Handle(UserNameChanged ev) {
        // change cache with new event parameters
    }

    public void Refresh() {
        var session = _sessionFactory();
        // get user from db then create userinfo...
    }

    public Guid[] GetDirectChilds(Guid userId) {
        // code
    }

    public Guid[] GetAllChilds(Guid userId) {
           // code
    }

    public Guid GetDepartment(Guid userId) {
        // code
    }

    public class UserInfo
    {
        public Guid Id { get; set; }
        public string FullName { get; set; }
        public Guid? ParentId {get;set;}
    }
}

public class CachedTeamService
    : ITeamService,
    ICanRefreshCache,
    IEventHandler<TeamCreated>{
    // similar with CachedUserLookupService
}

我的注册:

container.RegisterManyForOpenGeneric(typeof(IEventHandler<>),
    (closedServiceType, implementations) =>
    {
        container.RegisterAll(closedServiceType, implementations);
    },
    applicationAssembly, typeof(Constants).Assembly);

var serviceRegistrations = 
    from type in applicationAssembly.GetExportedTypes()
    where type.Name.EndsWith("Service")
    where !type.IsAbstract
    where !type.IsInterface
    select new
    {
        Services = type.GetInterfaces(),
        Implementation = type
    };

var lifeStyles = new Dictionary<Type, Lifestyle>()
{
    {typeof(CachedUserLookupService),Lifestyle.Singleton},
    {typeof(CachedTeamService),Lifestyle.Singleton}
};

List<Type> cacheableComponents = new List<Type>();
foreach (var reg in serviceRegistrations)
{
    foreach (var service in reg.Services)
    {
        Lifestyle lifeStyle;
        if (lifeStyles.TryGetValue(reg.Implementation, out lifeStyle) == false)
        {
            lifeStyle = Lifestyle.Singleton;
        }

        if (typeof(ICanRefreshCache) == service)
        {
            cacheableComponents.Add(reg.Implementation);
            continue;
        }

        container.Register(service, reg.Implementation, lifeStyle);
    }
}

container.RegisterAll(typeof(ICanRefreshCache), cacheableComponents);

我想在系统启动时刷新所有缓存,使用ICanRefreshCache-&gt;刷新方法,所以我打电话:

第1步:

container.GetAllInstances<ICanRefreshCache>().Each(c=>c.Refresh());

第2步:

在我调用IEventHandler<UserNameChanged>或任何其他接口后随时输入属于CachedUserLookupService(或CachedTeamService)的接口,返回实例与Step 1实例不同,因此这个注册对我没有帮助。

我需要使用简单注射器注册来提供以下呼叫。

// must return Singleton CachedUserLookupService + other 
// IEventHandler<UserNameChanged> implementations
container.GetAllInstances<IEventHandler<UserNameChanged>>(); 

// must return Singleton CachedTeamService + other 
// IEventHandler<TeamCreated> implementations
container.GetAllInstances<IEventHandler<TeamCreated>>();  

// must return Singleton CachedUserLookupService
container.GetInstance<IUserLookupService>();

// must return Singleton CachedTeamService
container.GetInstance<ITeamService>();

// must return Singleton CachedUserLookupService + Singleton CachedTeamService
container.GetAllInstances<ICanRefreshCache>(); 

2 个答案:

答案 0 :(得分:8)

注意:此答案中的信息已过时。 Simple Injector v4中的情况发生了很大变化。

如果我理解正确,您有一个实现多个接口的组件,并且您希望每个注册都映射到该组件的同一个实例。因此,无论您是解析ITeamServiceICanRefreshCache还是IEventHandler<TeamCreated>,都希望获得CachedTeamService的相同实例。

在Simple Injector中执行此操作的一般方法是手动创建Registration实例并为每个接口注册,如下所示:

var registration = 
    Lifestyle.Singleton.CreateRegistration<CachedTeamService>(container);

container.AddRegistration(typeof(ITeamService), registration);
container.AddRegistration(typeof(ICanRefreshCache), registration);
container.AddRegistration(typeof(IEventHandler<TeamCreated>), registration);

这解释为here

然而,您的案例有点复杂,因为您使用RegisterManyForOpenGeneric和正常注册混合批量注册。因此,您应该禁止将您想要的IEventHandler<TeamCreated>批量注册为单身,或者完全需要replace the registration

但是,在这种情况下无法替换注册,因为Simple Injector不允许您替换作为集合一部分的注册。所以我们可以按如下方式禁止该类型的注册:

Type[] typesToRegisterManually = new[]
{
    typeof(CachedTeamService)
};

container.RegisterManyForOpenGeneric(typeof(IEventHandler<>),
    (service, impls) =>
    {
        container.RegisterAll(service, impls.Except(typesToRegisterManually));
    },
    assemblies);

var registration = 
    Lifestyle.Singleton.CreateRegistration<CachedTeamService>(container);

container.AddRegistration(typeof(ITeamService), registration);
container.AddRegistration(typeof(ICanRefreshCache), registration);

// Using SimpleInjector.Advanced
container.AppendToCollection(typeof(IEventHandler<TeamCreated>), registration);

我的经验是,复杂的注册通常表示代码中存在SOLID原则违规。很难对您的设计提供任何具体的反馈,但我发现具有这些多个界面的类很可能有多个职责并且有多个理由需要更改(它们违反SRP),导致您在添加新功能时更改它们(这是OCP违规行为)。

相反,我可以提出一些建议:

  • 将通用IEventHandler<T>的逻辑移动到自己的类中。事件处理程序显然是一个不同的责任,接口名称已经告诉你。然后,这个类可以依赖于从中提取逻辑的旧类,或者将事件处理程序所依赖的逻辑提取到它自己的类中,并让“旧”类“使用”。 class和事件处理程序都依赖于这个新的共享类。
  • IUserLookupService接口本身就像是伪装的存储库,这基本上意味着您违反了SRP,OCP和ISP(如this article中所述)。正如that article所描述的那样,自定义查询值得拥有自己的抽象:IQueryHandler<TQuery, TResult>。这使得对它们应用横切关注点变得非常容易,使它们成为SOLID,并且使用container.RegisterManyForOpenGeneric注册它们非常简单。

除了类实现ICanRefreshCache的情况之外,这在大多数情况下只是实现一个抽象的类。对于这种特殊情况,我建议您使用composite ICanRefreshCache实现,以便在应用程序中触发所有ICanRefreshCache。但是,不是将IEnumerable<ICanRefreshCache>注入到该组合中,而是将该组合作为Composition Root的一部分,并让它依赖于容器。这允许您在运行时搜索完整配置以查找所有ICanRefreshCache实现。

这就是这种复合材料的样子:

public class CanRefreshCacheComposite : ICanRefreshCache
{
    private readonly Lazy<InstanceProducer[]> canRefreshProducers;

    public CanRefreshCacheComposite(Container container) {
        this.canRefreshProducers = 
            new Lazy<InstanceProducer[]>(() => GetProducers(container).ToArray());
    }

    public void Refresh() {
        foreach (var producer in this.canRefreshProducers.Value) {
            var refresher = (ICanRefreshCache)producer.GetInstance();
            refresher.Refresh();
        }
    }

    private IEnumerable<InstanceProducer> GetProducers(Container container) {
        return
            from producer in container.GetCurrentRegistrations()
            where typeof(ICanRefreshCache).IsAssignableFrom(
                producer.Registration.ImplementationType)
            select producer;
    }
}

您可以按如下方式注册:

container.RegisterSingle<ICanRefreshCache, CanRefreshCacheComposite>();

// To make sure all all ICanRefreshCache implementations that are part of
// a collection are known to the container, call Verify() when you're done
// registering.
container.Verify();

通过这种方式,您可以简单地依赖代码中的ICanRefreshCache,在其上调用Refresh方法,然后复合将完成剩下的工作。

答案 1 :(得分:0)

我使用以下注册解决了我的问题,但我认为还有另一种方法。

List<Registration> cacheableComponents = new List<Registration>();
foreach (var reg in serviceRegistrations)
{
     Lifestyle lifeStyle;
     if (lifeStyles.TryGetValue(reg.Implementation, out lifeStyle) == false)
     {
             lifeStyle = Lifestyle.Singleton;
     }

     Registration registration = null;
     if (lifeStyle == Lifestyle.Singleton)
     {
         registration = Lifestyle.Singleton.CreateRegistration(reg.Implementation, container);
     }
     else
     {
         registration = Lifestyle.Transient.CreateRegistration(reg.Implementation, container);
     }

     foreach (var service in reg.Services)
     {
       if (typeof(ICanRefreshCache) == service)
         {
             cacheableComponents.Add(registration);
             continue;
         }

          container.AddRegistration(service,registration);
     }
}
container.RegisterAll(typeof(ICanRefreshCache), cacheableComponents);
相关问题