DI创作模式

时间:2013-04-24 16:30:22

标签: dependency-injection inversion-of-control castle-windsor

现在已经挣扎了很长一段时间,所以我开始认为我创造了一种反模式。不过,这里也是如此;

//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。

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'和主服务所依赖的任何其他依赖项,也依赖于项目,  引发异常,说未找到项目服务。

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)..这是维护中的噩梦。

更新:我在下面的答案中添加了一些单元测试,希望能够解决一些问题

2 个答案:

答案 0 :(得分:1)

请检查您是否可以使用以下方法使用范围:

[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 :(得分:0)

对于一些奇怪的(但可能是有效的)原因,Windsor子容器可以访问其父容器,但不能相反。这意味着为了使用新容器中主容器中注册的服务,我们必须设置主容器的Parent而不是新容器的Parent。

这很不方便,因为容器只能有一个父级。

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

或复制和粘贴:

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;
            });
        }
    }
}