深入理解 NetCore 中的依賴注入的好處 及 、Singleton、Scoped、Transient 三種對象的差異


十年河東,十年河西

莫欺少年窮

NetCore中依賴注入無處不在,關於依賴注入的好處,想必大家都能想到二個字:解耦

但依賴注入是如何做到解耦的呢?

下面以具體實例來描述,如下:

首先,在項目中創建一個發送消息的接口及實現類

    public interface IMessage
    {
        string SendMessage();
    }

    /// <summary>
    /// 傳真發送消息類
    /// </summary>
    public class MessageService_ChuanZhen:IMessage
    {
        public string SendMessage()
        {
            return "90年代的我使用傳真發送消息";
        }
    }
View Code

傳統的方式是這樣調用此方法的

    public class MessageController : Controller
    {
        IMessage service = new MessageService_ChuanZhen();
        public ViewResult Index()
        {
            var result = service.SendMessage();
            return View();
        }
    }
View Code

代碼上沒有任何問題,但隨着時代的發展,傳真發送消息過時了,現在需要使用郵件的方式發送消息,那么我們的實現如下:

增加郵件發送類、

    /// <summary>
    /// 郵箱發送消息類
    /// </summary>
    public class MessageService_Email : IMessage
    {
        public string SendMessage()
        {
            return "21世紀我使用郵件發送消息";
        }
    }
View Code

修改控制器代碼,如下:

    public class MessageController : Controller
    {
        IMessage service = new MessageService_Email();
        public ViewResult Index()
        {
            var result = service.SendMessage();
            return View();
        }
    }
View Code

從上述代碼可以看出,我們要向發送消息,就必須完全依賴創建的Service對象,

首先在startpUP.cs中注冊服務,如下:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IMessage, MessageService_ChuanZhen>();


            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });


            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }
View Code

然后在控制器中注入服務對象,如下:

    public class MessageController : Controller
    {
        private readonly IMessage _messageService;
        public MessageController(IMessage MessageService)
        {
            _messageService = MessageService;
        }

        public ViewResult Index()
        {
            var result = _messageService.SendMessage();
            return View();
        }
    }
View Code

根據上述代碼,無論你將來增加多少中通信方式,我的控制器都不依賴於具體的service對象,我要要做的就是擴展我們的接口實現類及在startUP.cs中的ConfigureServices方法中重新注冊服務即可,在這里,我們可以將ConfigureServices方法看做一個注接口對應冊服務類大容器你需要什么服務,你就去注冊好了,客戶端無需修改任何代碼

譬如,現在我們需要將傳真方式修改為郵件方式,只需修改下我們注冊的服務

services.AddSingleton<IMessage, MessageService_ChuanZhen>();

修改為:

 services.AddSingleton<IMessage, MessageService_Email>();

這樣就做到了完美解耦,我們的控制器也就不再依賴於bou某個具體的對象了。

我們書寫這樣的代碼,也符合設計模式中的:繼承原則,單一職責原則,開放封閉原則,依賴倒轉原則。

上述說的設計模式原則簡單介紹下:

關於繼承無需多說

所謂單一職責原則是指:就一個類而言,應該僅有一個引起它變化的原因

所謂開閉原則是指:對於擴展是開放的,對於修改是封閉的(ASD原則)

依賴倒轉原則是指:高層不應該依賴底層模塊(強內聚,松耦合),就想上述代碼中的控制器屬於高層模塊,接口及其實現類,服務注冊類/方法(startup.cs中的ConfigureServices)屬於底層模塊。

截止到這兒,我們就把依賴注入的好處說完了,下面介紹下本文的重點,NetCore的三種不同類型的對象

netcore提供了三種不同類型的對象,分別為:全局單例對象(AddSingleton),作用域單例對象(AddScoped)、臨時對象(AddTransient)

具體還是結合代碼來說明:

首先創建接口,如下:

    /// <summary>
    /// 全局的
    /// </summary>
    public interface ITestService_Singleton
    {
        Guid MyProperty { get; }
    }

    /// <summary>
    /// 作用域內的
    /// </summary>
    public interface ITestService_Scoped
    {
        Guid MyProperty { get; }
    }
    
    /// <summary>
    /// 臨時的
    /// </summary>
    public interface ITestService_Transient
    {
        Guid MyProperty { get; }
    }
View Code

其次,創建接口的實現類,如下:

    public class TestService_Singleton : ITestService_Singleton
    {
        public TestService_Singleton()
        {
            MyProperty = Guid.NewGuid();
        }
        public Guid MyProperty { get; set; }
    }
    public class TestService_Scoped : ITestService_Scoped
    {
        public TestService_Scoped()
        {
            MyProperty = Guid.NewGuid();
        }
        public Guid MyProperty { get; set; }
    }
    public class TestService_Transient : ITestService_Transient
    {
        public TestService_Transient()
        {
            MyProperty = Guid.NewGuid();
        }
        public Guid MyProperty { get; set; }
    }
View Code

然后,將接口與實現類注冊在startUp類中,如下:

        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<MyOptions>(Configuration);
            //全局單例對象  全局單例
            services.AddSingleton<ITestService_Singleton, TestService_Singleton>();
            //作用域內單例對象 作用域內不會重新創建
            services.AddScoped<ITestService_Scoped, TestService_Scoped>();
            //臨時對象,每次都回重新創建
            services.AddTransient<ITestService_Transient, TestService_Transient>();
            //
            services.AddSingleton<IMessage, MessageService_Email>();


            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });


            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }
View Code

最后,我們書寫控制器代碼,如下:

    public class HomeController : Controller
    {
        /// <summary>
        /// 全局的
        /// </summary>
        private ITestService_Singleton _singletonService;
        /// <summary>
        /// 作用域內的
        /// </summary>
        private ITestService_Scoped _scopedService;
        /// <summary>
        /// 臨時的
        /// </summary>
        private ITestService_Transient _transientService;
        public HomeController(ITestService_Singleton SingletonService
            , ITestService_Scoped ScopedService
            , ITestService_Transient TransientService)
        {
            _singletonService = SingletonService;
            _scopedService = ScopedService;
            _transientService = TransientService;
        }

        /// <summary>
        ///  //這里采用了Action注入的方法
        /// </summary>
        /// <param name="singletonService_2"></param>
        /// <param name="ScopedService_2">保證和_scopedService在同一個作用域</param>
        /// <param name="TransientService_3"></param>
        /// <returns></returns>
        public IActionResult Index([FromServices]ITestService_Singleton singletonService_2, [FromServices]ITestService_Scoped ScopedService_2,[FromServices]ITestService_Transient TransientService_2)
        {
            ViewData["Message_1"] = "全局對象生成的GUID:" + _singletonService.MyProperty;
            ViewData["Message_12"] = "全局對象生成的GUID:" + singletonService_2.MyProperty;

            ViewData["Message_2"] = "作用域內對象生成的GUID:" + _scopedService.MyProperty;
            ViewData["Message_22"] = "作用域內對象生成的GUID:" + ScopedService_2.MyProperty;

            ViewData["Message_3"] = "臨時對象生成的GUID:" + _transientService.MyProperty;
            ViewData["Message_32"] = "臨時對象生成的GUID:" + TransientService_2.MyProperty;
            return View();
        }
        
    }
View Code

執行程序,通過結果,我們來分析:

 

 

 

 

 

 由上圖可以看出,全局單例的對象生成的GUID每次都是同一個結果。

作用域生成的結果,只有在同一作用域時才會一樣

臨時對象每次都不一樣。

參考博客:

https://www.cnblogs.com/zhangzhiping35/p/11058761.html

https://docs.microsoft.com/zh-cn/aspnet/core/mvc/controllers/dependency-injection?view=aspnetcore-3.1

https://www.cnblogs.com/GuZhenYin/p/8297145.html  

 https://www.cnblogs.com/chenwolong/p/yz.html

@天才卧龍的博客


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM