簡體   English   中英

如何解決Autofac循環依賴?

[英]How to solve Autofac circular dependency?

最近開始使用 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 個選項:

  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 參考

另一種解決方法是在依賴服務的構造函數中使用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;
    }
}

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 但這是另一回事)

您需要使它們在實現中互斥。 例子:

  1. 您可以從獲取設置中刪除日志記錄
  2. 您可以從記錄器中刪除設置檢查

此處的循環引用表明您可能沒有以易於維護的方式執行此操作(即您將具有更高的耦合度)。

如果您想保持代碼原樣並使其正常工作,您可以將 log 方法和 getsettings 方法設為靜態。 在我看來,這是一個 hack,您應該嘗試頂部列出的選項 1 或 2。 這樣做的原因是因為在我看來,使某些東西成為靜態不應該改變代碼的行為,而是應該用於內存優化(即,請參閱本領域的一些類似閱讀的單例反模式)。

對於您的代碼,我建議您從 appsettingsprovider 中刪除日志記錄,而是使用它的記錄器啟動來添加圍繞該類使用的日志語句。 或者你可以探索:

  1. 工廠模式嘗試包裝您的任何一個類的創建。
  2. 最后,在 C# 中,您可以使用 lambda / 函數屬性將實例傳遞給類,這樣引用就不會遞歸地創建新實例。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM