如何解决Autofac的循环依赖问题?

4

最近开始使用autofac,我遇到了循环依赖问题,而在使用Unity时,我已经克服了这个问题。以下是我的代码:

 void Main()
 {
    var builder = new ContainerBuilder();

    // 1) Every class needs logger
    // 2) Logger needs AppSettingsProvider which needs AppEnvironmentProvider
    // 3) Listener needs AppSettingsProvider which needs AppEnvironmentProvider

    builder.RegisterType<Logger>().As<ILogger>().SingleInstance();
    builder.RegisterType<AppSettingsProvider>().As<IAppSettingsProvider>().SingleInstance();
    builder.RegisterType<AppEnvironmentProvider>().As<IAppEnvironmentProvider>().SingleInstance();

    builder.RegisterType<Listener>().As<IListener>().SingleInstance();

    var container = builder.Build();

    var listener = container.Resolve<IListener>();
    listener.Do();

 }

 public interface IListener
 { 
    string Do();
 }

 public class Listener : IListener
 { 
    IAppSettingsProvider appSettingsProvider;
    public Listener(IAppSettingsProvider appSettingsProvider)
    {
        // this class needs IAppSettingsProvider to get some settings
        // but not actually used on this example.
        this.appSettingsProvider = appSettingsProvider;
    }
    public string Do()
    {
        return "doing something";
    }
 }

 public interface ILogger
 { 
    void Log(string message);
 }

 public class Logger : ILogger
 {
    IAppSettingsProvider appSettingsProvider;
    public Logger(IAppSettingsProvider appSettingsProvider)
    {
        this.appSettingsProvider = appSettingsProvider;
    }

    public void Log(string message)
    {
        // simplified
        if (this.appSettingsProvider.GetSettings())
        {
            Console.WriteLine(message);
        }
    }
 }

 public interface IAppSettingsProvider
 { 
    // will return a class, here simplified to bool
    bool GetSettings();
 }

 public class AppSettingsProvider : IAppSettingsProvider
 { 
    ILogger logger;
    public AppSettingsProvider(ILogger logger)
    {
        this.logger = logger;
    }

    public bool GetSettings()
    {
        this.logger.Log("Getting app settings");

        return true;
    }
 }


 public interface IAppEnvironmentProvider
 { 
    string GetEnvironment();
 }

 public class AppEnvironmentProvider : IAppEnvironmentProvider
 { 
    ILogger logger;
    public AppEnvironmentProvider(ILogger logger)
    {
        this.logger = logger;
    }
    public string GetEnvironment()
    {
        this.logger.Log("returning current environment");

        return "dev";
    }
 }

任何有关解决此问题的指示都将是有帮助的。

2
循环依赖不是通常想要解决的问题。这可能意味着架构可能需要重新设计。但是,如果您真的必须快速解决该问题,请尝试为其中一个注入使用属性注入而不是构造函数注入。 - gretro
你是如何在Unity中克服这个问题的? - Steven
4个回答

5

另一个解决方案是在依赖服务的构造函数中使用 Lazy<ILogger>

public class AppSettingsProvider : IAppSettingsProvider
{
    Lazy<ILogger> logger;
    public AppSettingsProvider(Lazy<ILogger> logger)
    {
        this.logger = logger;
    }
    public bool GetSettings()
    {
        this.logger.Value.Log("Getting app settings");
        return true;
    }
}

5
您有两个选项:
  1. 使用属性注入
  2. 使用工厂(这可能会导致您与工厂之间存在强依赖关系)
以下是使用属性注入的示例:
    static void Main()
    {
        var builder = new ContainerBuilder();

        // 1) Every class needs logger
        // 2) Logger needs AppSettingsProvider which needs AppEnvironmentProvider
        // 3) Listener needs AppSettingsProvider which needs AppEnvironmentProvider

        builder.RegisterType<Logger>().As<ILogger>().SingleInstance();

        builder.RegisterType<AppSettingsProvider>()
            .As<IAppSettingsProvider>()
            .SingleInstance()
            .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);

        builder.RegisterType<AppEnvironmentProvider>().As<IAppEnvironmentProvider>().SingleInstance();

        builder.RegisterType<Listener>().As<IListener>().SingleInstance();

        var container = builder.Build();

        var listener = container.Resolve<IListener>();

        Console.WriteLine(listener.Do());

        Console.Read();

    }

    public interface IListener
    {
        string Do();
    }

    public class Listener : IListener
    {
        IAppSettingsProvider appSettingsProvider;
        public Listener(IAppSettingsProvider appSettingsProvider)
        {
            // this class needs IAppSettingsProvider to get some settings
            // but not actually used on this example.
            this.appSettingsProvider = appSettingsProvider;
        }
        public string Do()
        {
            return "doing something using circular Dependency";
        }
    }

    public interface ILogger
    {
        void Log(string message);
    }

    public class Logger : ILogger
    {
        IAppSettingsProvider appSettingsProvider;
        public Logger(IAppSettingsProvider appSettingsProvider)
        {
            this.appSettingsProvider = appSettingsProvider;
        }

        public void Log(string message)
        {
            // simplified
            if (this.appSettingsProvider.GetSettings())
            {
                Console.WriteLine(message);
            }
        }
    }

    public interface IAppSettingsProvider
    {
        // will return a class, here simplified to bool
        bool GetSettings();
    }

    public class AppSettingsProvider : IAppSettingsProvider
    {
        ILogger logger;
        public AppSettingsProvider()
        {

        }

        public ILogger Logger { get; set; }

        public bool GetSettings()
        {
            Logger.Log("Getting app settings");

            return true;
        }
    }


    public interface IAppEnvironmentProvider
    {
        string GetEnvironment();
    }

    public class AppEnvironmentProvider : IAppEnvironmentProvider
    {
        ILogger logger;
        public AppEnvironmentProvider(ILogger logger)
        {
            this.logger = logger;
        }
        public string GetEnvironment()
        {
            this.logger.Log("returning current environment");

            return "dev";
        }
    }

这里有Autofac的建议: Autofac参考文档

3
Autofac提供了另一种解决循环依赖的选项。如果A需要B,B需要A,并且B在其构造函数中没有使用A,则B可以以一种称为“动态实例化”的方式使用A。请参见。

https://docs.autofac.org/en/latest/resolve/relationships.html#dynamic-instantiation-func-b

在这种情况下,B将使用A的工厂方法Func<A>来构造它自己的构造函数(并在构造函数之后以某种懒惰的方式调用它),以获取A的实例。
例如,在上述问题中更改Logger以解决循环依赖问题:
    public class Logger : ILogger
    {
        Func<IAppSettingsProvider> appSettingsProviderFactory;
        IAppSettingsProvider _appSettingsProvider;
        IAppSettingsProvider appSettingsProvider { get
            {
                if (_appSettingsProvider == null) _appSettingsProvider = appSettingsProviderFactory();
                return _appSettingsProvider;
            }
        }
        public Logger(Func<IAppSettingsProvider> appSettingsProviderFactory)
        {
            this.appSettingsProviderFactory = appSettingsProviderFactory;
        }

        public void Log(string message)
        {
            // simplified
            if (this.appSettingsProvider.GetSettings())
            {
                Console.WriteLine(message);
            }
        }
    }

(注:当我在Do()中添加appSettingsProvider.GetSettings();时,问题的代码进入了StackOverflowException。由于GetSettings调用Log,而Log又调用GetSettings,但这是另一件事)

2
您需要在实现上使它们互斥。例如:
  1. 您可以从获取设置中删除日志记录
  2. 您可以从记录器中删除设置检查
循环引用表明您可能没有以易于维护的方式完成此操作(即,您将具有更高级别的耦合)。
如果您想保留代码并对其进行修改以使其正常工作,则可以将日志方法和getsettings方法设为静态。在我的观点中,这是一种hack方法,您应该尝试前面列出的选项1或2。原因是,我的看法是,将某些东西设置为静态不应改变代码的行为,而应该用于内存优化(例如,参见单例反模式以了解此领域的一些类似读物)。
对于您的代码,我建议从appsettingsprovider中删除日志记录,并改为在loggers初始化时添加围绕使用该类的日志语句。或者您可以探索:
  1. 工厂模式,尝试包装任何一个类的创建。
  2. 最后,在C#中,您可以拥有一个lambda /函数属性,您可以使用它将一个实例传递给一个类,以便引用不会递归地创建新实例。

  • 我无法从任何类中删除日志记录
  • 我需要在日志记录器中使用设置提供程序
  • 我无法将方法设为静态,因为它们都有单元测试。 所以我只能使用工厂或在适当位置使用IocContainer.Resolve<ILogger>。你觉得呢?
- Amit
你可以将设置类分成两个类,然后导入记录器设置类,不要导入通用设置类,这样可以保留其日志记录功能。 - Dessus
你真的不应该使用lambda技术。虽然它可以工作,但它会放松你的契约(即当将函数作为对象传递时,很难知道如何执行函数)。它还会跨越单一责任原则。不遵循标准化设计模式是导致你面临问题的原因,所以最好不要遵循不良实践。 - Dessus
我会将记录器传递到这个方法中:public string GetEnvironment() - Dessus
如果需要语法糖,您可以在该类上放置一个工厂抽象。您可能不需要这样做。 - Dessus
我认为将记录器作为方法参数传递是合适的。谢谢! - Amit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接