Circular reference using IoC

2019-09-16 20:04发布

问题:

I am using windsor castle as my IoC container, and has run in to a bit of a problem.

First of all - i know about: Castle Windsor: How to prevent circular references in factory-created objects were the created objects refers back to the factory

But since circular reference is considered as "Code Smell" and i should consider refactoring app architecture i am asking anyway.

I have very similar situation:

public class OperationsFactory
{
    private GeneralSettingsManager m_generalSettings;
    private Dictionary<OperationType, OperationCreatorBase> m_creators;

    public OperationsFactory(IKernel container)
    {
        m_generalSettings = container.Resolve<GeneralSettingsManager>();
        var creators = container.ResolveAll<OperationCreatorBase>(); //FIRST DEPENDENCY

        foreach (var creator in creators)
        {
            m_creators.Add(creator.CreatorOperationType, creator);
        }
    }
.
.
.
    private OperationCreatorBase GetCreator(OperationType operationType)
    {
        return m_creators[operationType];
    }
}

Now i would like to take in code this OperationFactory from windsor container so i can easily read all the successors of OperationCreatorBase.

now there is a code for OperationCreator:

public class ConvertToFullOperationCreator : OperationCreatorBase
{

    private OperationsFactory m_operationsFactory;
    private SomeHelper m_someHelper;

    public ConvertToFullOperationCreator(IKernel container)
    {
       m_operationsFactory = container.Resolve<OperationsFactory>(); //SECOND dependency which causes error
       m_someHelper = container.Resolve<SomeHelper>();
    }

    public override OperationType CreatorOperationType
    {
        get { return OperationType.SomeOperation2; }
    }

    public override List<OperationBase> CreateOperation(FileData fileData)
    {
          //HERE I WANT TO USE FACTORY to get creators for SUBOPERATIONS
           var creator1 = m_operationsFactory.GetCreator(OperationType.SomeSuboperation1);
           creator1.CreateOperation(fileData);
            .
            .
            .

            m_someHelper.DoSomething(fileData);

           var creator2 = m_operationsFactory.GetCreator(OperationType.SomeSuboperation2);
           creator2.CreateOperation(fileData);
            .
            .
            .
      }
}

I really want to use windsor castle for both of this classes because i am using more components (such as SomeHelper in creator... and more). In factory class i am using nice method ResolveAll provided by IKernel.

There is obvious constructor circular reference but i cant figure out, whats wrong with this component design and most important - how to make this runnable.

I know i can do it with Property Injection on both sides but this kills this nice dependency injection feature, so thats why the answer said in upper stackoverflow question wont solve my problem. Am i missing something?

Is there any suggestion how to redesign those two components or how to Split the "C" class said in nice article about circular reference here: http://misko.hevery.com/2008/08/01/circular-dependency-in-constructors-and-dependency-injection/

回答1:

In order to solve cyclic dependency you should inject Func<OperationsFactory> instead of OperationsFactory via constructor (or resolve using IKernel/ IWindsorContainer).

public class ConvertToFullOperationCreator : OperationCreatorBase
{        
    private Func<OperationsFactory> get_operationsFactory;
    private SomeHelper m_someHelper;

    public ConvertToFullOperationCreator(
            SomeHelper someHelper,
            Func<OperationsFactory> get_operationsFactory)
    {
       this.get_operationsFactory = get_operationsFactory
       m_someHelper = someHelper;
    }

    public override List<OperationBase> CreateOperation(FileData fileData)
    {
          var m_operationsFactory = get_operationsFactory()
          // Here you can place all your code
          var creator1 = m_operationsFactory
                .GetCreator(OperationType.SomeSuboperation1);
          ...
          var creator2 = m_operationsFactory
                .GetCreator(OperationType.SomeSuboperation2);
          ...
      }
}

First OperationsFactory should be registered, then Func<OperationsFactory>.

container.Register(Component.For<Func<OperationsFactory>>()
    .UsingFactoryMethod(container =>
    {
        Func<OperationsFactory> func = container.Resolve<OperationsFactory>;
        return func;
    }));

I've already answered the similar question Cyclic dependency with Castle Windsor IoC for NHibernate ISession. You can find more details there.

If you already use IoC container it is better to inject instances of concrete types via constructor instead of IKernel. IKernel is a part of your infrastructure.

In order to resolve IEnumerable<T> CollectionResolver can be used.

public class OperationsFactory
{
    private GeneralSettingsManager m_generalSettings;
    private Dictionary<OperationType, OperationCreatorBase> m_creators;

    public OperationsFactory(
            GeneralSettingsManager generalSettings,
            IEnumerable<OperationCreatorBase> creators)
    {
        m_generalSettings = generalSettings;
        foreach (var creator in creators)
        {
            m_creators.Add(creator.CreatorOperationType, creator);
        }
    }
    ...
}

EDIT :

If you cannot register Func<OperationsFactory> you can create it in the constructor in order to load OperationsFactory lazily.

public class ConvertToFullOperationCreator : OperationCreatorBase
{        
    private Func<OperationsFactory> get_operationsFactory;
    private SomeHelper m_someHelper;

    public ConvertToFullOperationCreator(
            IKernel container)
    {
       this.get_operationsFactory = () => container.Resolve<OperationsFactory>;
       m_someHelper = container.Resolve<SomeHelper>();
    }

    public override List<OperationBase> CreateOperation(FileData fileData)
    {
          var m_operationsFactory = get_operationsFactory()
          // Here you can place all your code
          var creator1 = m_operationsFactory
                .GetCreator(OperationType.SomeSuboperation1);
          ...
          var creator2 = m_operationsFactory
                .GetCreator(OperationType.SomeSuboperation2);
          ...
      }
}