首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >DI创作模式

DI创作模式
EN

Stack Overflow用户
提问于 2013-04-25 00:30:22
回答 2查看 92关注 0票数 0

我已经在这个问题上挣扎了很长一段时间,所以我开始认为我已经创建了一个反模式。尽管如此,还是要开始了;

代码语言:javascript
复制
//Register self
container.Register(Component.For<IWindsorContainer>().Instance(container));
//Register all
container.Register(Component.For<IService1>().ImplementedBy<Service1>());
container.Register(Component.For<IService2>().ImplementedBy<Service2>());
//etc

IService1
{
  //blabla
}
IService2 {IService1 Service1{get;}}

因此,可以创建IService1和IService2而不需要任何特殊操作。从IService3开始,就涉及到IProject。

代码语言:javascript
复制
IProject{}
//Resolve a service that, amongst other things, relies on an IProject
IProjectGet
{
    T Get<T>(IProject proj) 
        where T : class;
}
//Impl
ProjectGet : IProjectGet
{
    IWindsorContainer _cont;
    public ProjectGet(IWindsorContainer cont){_cont=cont}

    public T Get<T>(IProject proj)
    {
        //Resolve using the main (and only) container and pass the IProject
        return _cont.Resolve<T>(new {p = proj});
    }
}

这不起作用,只有主服务被解析为'p = proj‘和主服务所具有的任何其他依赖项,这些依赖项也依赖于项目,导致未找到项目服务的异常。

代码语言:javascript
复制
IService3 
{
    IService2 Service2{get;}
    IProjectGet ProjectGet{get;}
    IProjectLevelStuff SetActiveProject(IProject proj);
}
Service3 : IService3 
{
    IService2 Service2{get;private set;}
    IProjectGet ProjectGet{get;private set;}

    public Service3(IService2 s2, IProjectGet p)
    {
        ProjectGet = p;
        Service2 = s2;
    }

    public IProjectLevelStuff SetActiveProject(IProject proj)
    {
        return ProjectGet.Get<IProjectLevelStuff>(proj);
    }
}
ProjectLevelStuff : IProjectLevelStuff
{
    IProject Project{get;private set;}
    IService4 Service4 {get;private set;}

    public ProjectLevelStuff(IProject p, IService4)//etc.
}
IService4
{
    IService2 Service2{get;}
    IService5 Service5{get;}
    IService6 Service6{get;}
    IProject Project{get;}
}
IService5{IProject Project{get;}}
IService6{IProject Project{get;}}

这会失败,因为只有ProjectLevelStuff会将IProject传递给它,而且由于IService4及其依赖项也需要它,因此会抛出一个异常。即使这确实有效,我也不喜欢它,因为每个依赖于IProject的服务都被强制调用参数'p‘,这是我想要避免的。

我只想继续使用我已经拥有的服务,但这次添加了传递给我们的泛型Get方法的IProject实例作为一个可解析的依赖项。我发现没有办法复制容器并创建一个新的容器,然后将主容器添加为子容器并不会改变任何东西(依赖项仍然缺失)。这是怎么做的?

Castle Windsor确实有一个内置的TypeFactory,但它基本上和我已经在做的事情是一样的,并且不能解决任何问题。我找到的唯一的“解决方案”是创建一个新的容器并重新注册类型,但这一次通过主容器解析它们(当然,除了IProject )。这是一个工作中的维护噩梦。

更新:我在下面的答案中添加了一些单元测试,希望能澄清一些事情

EN

回答 2

Stack Overflow用户

发布于 2013-04-29 15:58:30

请检查您是否可以使用以下使用作用域的方法:

代码语言:javascript
复制
[SetUp]
public void Setup()
{
    int counter = 0;

    _container = new WindsorContainer();
    _container.AddFacility<TypedFactoryFacility>();
    _container.Register(
        Component.For<IService1>().ImplementedBy<Service1>().LifestyleScoped(),
        Component.For<IService2>().ImplementedBy<Service2>().LifestyleScoped(),
        Component.For<IService3>().ImplementedBy<Service3>().LifestyleScoped(),
        Component.For<Class1>().LifestyleTransient(),
        Component.For<Class2>().LifestyleTransient(),
        Component.For<IProject>().ImplementedBy<Project>().LifestyleScoped().DynamicParameters((k, d) => d["name"] = "MyProjectName"+counter++)
        );
}

[Test]
public void TestClass1()
{
    using (_container.BeginScope())
    {
        Class1 object1 = _container.Resolve<Class1>();;
        var object2 = _container.Resolve<Class1>();
        Assert.AreNotSame(object1, object2);

        Assert.AreSame(object1.Service1, object2.Service1);
    }
}

[Test]
public void TestClass2()
{
    Class2 object1;
    using (_container.BeginScope())
    {
        object1 = _container.Resolve<Class2>();
        var object2 = _container.Resolve<Class2>();
        Assert.AreNotSame(object1, object2);

        Assert.AreSame(object1.Project, object2.Project);
        Assert.AreSame(object1.Service2.Project, object2.Service2.Project);
    }

    Class2 object3;
    using (_container.BeginScope())
    {
        object3 = _container.Resolve<Class2>();
    }

    Assert.AreNotSame(object1.Project, object3.Project);
}
票数 1
EN

Stack Overflow用户

发布于 2013-04-25 21:52:45

出于某种奇怪的(但可能是有效的)原因,Windsor子容器可以访问它们的父容器,但反过来却不能。这意味着为了从新容器中使用在主容器中注册的服务,我们必须设置主容器的父容器,而不是新容器的父容器。

这是非常不方便的,因为一个容器只能有一个父级。

代码语言:javascript
复制
internal class ProjServices : IProjServices
{
    private readonly IKwProject _proj;
    private readonly IWindsorContainer _mainCont;

    public ProjServices(IKwProject proj, IWindsorContainer mainCont)
    {
        _mainCont = mainCont;
        _proj = proj;
    }

    public T Resolve<T>()
    {
        T rett;

        //Create new container
        var projCont = new WindsorContainer();
        //Register new service
        projCont.Register(Component.For<IKwProject>().Instance(_proj));

        //Set hierarchy
        lock (_mainCont)
        {
            projCont.AddChildContainer(UiContainer); //ui needs project, set parent to projCont
            UiContainer.AddChildContainer(_mainCont); //main needs ui, set parent to uiCont

            //Resolve using main, which now has access to UI and Project services
            try
            {
                rett = _mainCont.Resolve<T>();
            }
            finally
            {
                projCont.RemoveChildContainer(UiContainer);
                UiContainer.RemoveChildContainer(_mainCont);
            }
        }

        return rett;
    }

    private static readonly object UIContainerLock = new object();
    private static volatile IWindsorContainer _uiContainer;
    private static IWindsorContainer UiContainer
    {
        get
        {
            if(_uiContainer==null)
                lock(UIContainerLock)
                    if (_uiContainer == null)
                    {
                        //Register the UI services
                    }
            return _uiContainer;
        }
    }
}

现在,如果我将来想在更新的容器中使用这些新容器,我想我会因为单亲-唯一的事情而再次被卡住……我该如何正确地做到这一点?

更新:

VS 2010和2012的单元测试:

ServiceTest.zip (798 KB) https://mega.co.nz/#!z4JxUDoI!UEnt3TCoMFVg-vXKEAaJrhzjxfhcvirsW2hv1XBnZCc

或复制和粘贴:

代码语言:javascript
复制
using System;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ServiceTest
{
    /// <summary>
    /// A service that doesn't rely on anything else
    /// </summary>
    public interface IService1
    {
    }
    class Service1 : IService1
    {
    }

    /// <summary>
    /// The Project
    /// </summary>
    public interface IProject
    {
        string Name { get; }
    }
    public class Project : IProject
    {
        public Project(string name)
        {
            Name = name;
        }

        public string Name { get; private set; }
    }

    /// <summary>
    /// A Service that relies on a Project
    /// </summary>
    public interface IService2
    {
        IProject Project { get; }
        string GetProjectName();
    }
    /// <summary>
    /// The implementation shows it also relies on IService3
    /// </summary>
    public class Service2 : IService2
    {
        public Service2(IProject project, IService3 service3)
        {
            Project = project;
            Service3 = service3;
        }

        public IProject Project { get; private set; }
        public IService3 Service3 { get; private set; }

        public string GetProjectName()
        {
            return Project.Name;
        }
    }
    /// <summary>
    /// IService3 is a Service that also relies on the Project
    /// </summary>
    public interface IService3
    {
        IProject Project { get; }
    }
    public class Service3 : IService3
    {
        public Service3(IProject project)
        {
            Project = project;
        }

        public IProject Project { get; private set; }
    }

    /// <summary>
    /// Class1 uses the service without any dependencies so it will be easy to resolve
    /// </summary>
    public class Class1
    {
        public Class1(IService1 service1)
        {
            Service1 = service1;
        }

        public IService1 Service1 { get; private set; }
    }

    /// <summary>
    /// Class2 also uses that service, but it also relies on a Project ánd IService2
    ///  which as you know also relies on the Project and IService3 which also relies on 
    ///  the Project
    /// </summary>
    public class Class2
    {
        public Class2(IService1 service1, IProject project, IService2 service2)
        {
            Service1 = service1;
            Project = project;
            Service2 = service2;
        }

        public IProject Project { get; private set; }
        public IService1 Service1 { get; private set; }
        public IService2 Service2 { get; private set; }
    }

    /// <summary>
    /// Set up the base services
    /// </summary>
    [TestClass]
    public class UnitTestBase
    {
        protected WindsorContainer Cont;

        [TestInitialize]
        public void BaseSetup()
        {
            Cont = new WindsorContainer();
            Cont.Register(Component.For<IService1>().ImplementedBy<Service1>().LifestyleTransient());
            Cont.Register(Component.For<IService2>().ImplementedBy<Service2>().LifestyleTransient());
            Cont.Register(Component.For<IService3>().ImplementedBy<Service3>().LifestyleTransient());

            Cont.Register(Component.For<Class1>().LifestyleTransient());
            Cont.Register(Component.For<Class2>().LifestyleTransient());
        }

        [TestMethod]
        public void Class1_Resolves()
        {
            Cont.Resolve<Class1>();
        }
    }

    /// <summary>
    /// Set up the base unit tests
    /// </summary>
    [TestClass]
    public class UnitTestClass2Base : UnitTestBase
    {
        protected void RunTest3Times(Func<string, IWindsorContainer> getContainer)
        {
            const string projNameBase = "MyProjectName";
            Func<int, string> getProjectName = i => projNameBase + i;

            for (var i = 0; i < 3; i++)
            {
                var pName = getProjectName(i);
                GetClass2ForProject(getContainer(pName), pName);
            }
        }

        protected void GetClass2ForProject(IWindsorContainer cont, string projName)
        {
            var c2 = cont.Resolve<Class2>();

            Assert.IsTrue(c2.Project.Name == projName);
            Assert.IsTrue(c2.Service2.Project.Name == projName);
            Assert.IsTrue(c2.Service2.GetProjectName() == projName);
        }
    }

    /// <summary>
    /// This will fail on the second request because we cannot 
    ///  overwrite the earlier registration. And iirc containers can't
    ///  be altered after the first resolve.
    /// </summary>
    [TestClass]
    public class Attempt_1 : UnitTestClass2Base
    {
        [TestMethod]
        public void Class2_Resolves_Project_Scoped_Requests()
        {
            RunTest3Times(s =>
                {
                    Cont.Register(Component.For<IProject>().Instance(new Project(s)));
                    return Cont;
                });
        }
    }

    /// <summary>
    /// It looks like we have to create a new container for every Project
    /// So now the question remains; how do we get to keep using the base IService implementations
    ///  in the container that is scoped for the IProject?
    /// </summary>
    [TestClass]
    public class Attempt_2 : UnitTestClass2Base
    {
        static IWindsorContainer CreateContainer(IProject p)
        {
            var ret = new WindsorContainer();
            ret.Register(Component.For<IProject>().Instance(p));
            return ret;
        }

        /// <summary>
        /// This will fail because the services in the main 
        ///  container can't access the IProject in the new container
        /// </summary>
        [TestMethod]
        public void Class2_Resolves_Project_Scoped_Requests_1()
        {
            RunTest3Times(s =>
            {
                //Add the project container as a Child to the Main container
                var projCont = CreateContainer(new Project(s));
                Cont.AddChildContainer(projCont);
                return Cont;
            });
        }

        /// <summary>
        /// Doing the previous approach the other way around works.
        /// But now we can only resolve one thing at a time
        /// </summary>
        [TestMethod]
        public void Class2_Resolves_Project_Scoped_Requests_2()
        {
            IWindsorContainer projCont = null;

            //Add the Main container as a Child to the project container
            // (in other words set the Parent of Main to Project)
            // and then resolve using the main container.
            //A container can only have one parent at a time so we can only
            // resolve one scoped thing at a time.
            RunTest3Times(s =>
                {
                    if (projCont != null)
                        projCont.RemoveChildContainer(Cont);

                    projCont = CreateContainer(new Project(s));
                    projCont.AddChildContainer(Cont);
                    return Cont;
                });
        }

        /// <summary>
        /// The only way around that issue seems to be to register all project-dependent 
        ///  services in the new container. Then re-register all original services
        ///  in the new container and pass the resolving on to the main container; 
        ///  a maintenance nightmare and especially painful for named registrions.
        /// </summary>
        [TestMethod]
        public void Class2_Resolves_Project_Scoped_Requests_3()
        {
            Func<IProject, IWindsorContainer> createContainer2 = p =>
                {
                    var contNew = new WindsorContainer();

                    //Pass resolving of the non-dependent services on to the main container.
                    // this way it will respect it's lifestyle rules and not create new 
                    // instances of services we wanted to use as a singleton etc.
                    contNew.Register(Component.For<IService1>().UsingFactoryMethod(() => Cont.Resolve<IService1>()).LifestyleTransient());
                    contNew.Register(Component.For<Class1>().UsingFactoryMethod(() => Cont.Resolve<Class1>()).LifestyleTransient());

                    //Register the dependent services directly in the new container so they can access the project
                    contNew.Register(Component.For<IService2>().ImplementedBy<Service2>().LifestyleTransient());
                    contNew.Register(Component.For<IService3>().ImplementedBy<Service3>().LifestyleTransient());
                    contNew.Register(Component.For<Class2>().LifestyleTransient());

                    contNew.Register(Component.For<IProject>().Instance(p));

                    return contNew;
                };

            RunTest3Times(s =>
            {
                var projCont = createContainer2(new Project(s));
                return projCont;
            });
        }
    }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/16197371

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档