My.Ioc 程式碼範例——避免循環相依性

來源:互聯網
上載者:User

標籤:style   blog   http   color   io   os   使用   ar   strong   

本文的目的在於通過一些樣本,向使用者說明 My.Ioc 支援哪些類型的依賴關係。也就是說,如何設計對象不會導致循環相依性。

在 Ioc 世界中,循環相依性是一個頑敵。這不僅因為它會導致 Ioc 容器拋出異常,而且還因為它是不可預知的,儘管通過仔細的配置是可以盡量避免這個問題的。

當使用者在 Ioc 容器中註冊對象時,他們事先並不知道該對象與其他對象之間的依賴關係,因為依賴關係是由 Ioc 容器管理的。這種依賴關係要等到使用者首次調用 container.Resolve(contractType) 時才能確定。而且,它還有可能會隨著所依賴對象的註冊/登出而發生變化。

因此,要想避免循環相依性問題,那麼在設計對象時,仔細地思考該對象與其他對象之間的依賴關係,審慎地區分不同對象的職責就顯得比較重要了。當然,對象設計是 Ioc 容器之外的事情了,我們沒有辦法加以約束。但是,即使您不使用 Ioc 容器,對象設計也是一個值得您深思熟慮的大問題。而一旦您選擇使用 Ioc,那也意味著您的對象設計需要更多圍繞 Ioc 來考慮。

其實,在 My.Ioc 中要想避免循環相依性也很簡單,只要遵循一個原則即可,那就是 [不要在物件建構過程中引入循環相依性]。下面我們結合範例程式碼來講解。

using System;using System.Diagnostics;using My.Ioc;using My.Ioc.Exceptions;namespace HowToAvoidCyclicDependency{    #region Test clazz    #region Case 3    public class Grade3    {        private readonly Room3 _room;        public Grade3(Room3 room)        {            _room = room;        }        public Room3 Room        {            get { return _room; }        }    }    public class Room3    {        public Grade3 Grade { get; set; }    }    #endregion    #region Case 2    public class Grade2    {        private readonly Room2 _room;        public Grade2(Room2 room)        {            _room = room;            room.Grade = this;        }        public Room2 Room        {            get { return _room; }        }    }    public class Room2    {        public Grade2 Grade { get; set; }    }    #endregion    #region Case 1    public class Grade1    {        public Room1 Room { get; set; }    }    public class Room1    {        public Grade1 Grade { get; set; }    }    #endregion    #endregion    class Program    {        static void Main(string[] args)        {            var container = new ObjectContainer(false);            Register(container);            Resolve(container);            Console.WriteLine("HowToAvoidCyclicDependency success...");            Console.ReadLine();        }        static void Register(IObjectContainer container)        {            container.Register<Grade3>()                .In(Lifetime.Transient());            container.Register<Room3>()                .WithPropertyAutowired("Grade")                .In(Lifetime.Transient());            container.Register<Grade2>()                .In(Lifetime.Transient());            container.Register<Room2>()                .In(Lifetime.Transient());            container.Register<Grade1>()                .WithPropertyAutowired("Room")                .In(Lifetime.Transient());            container.Register<Room1>()                .WithPropertyAutowired("Grade")                .In(Lifetime.Transient());            container.CommitRegistrations();        }        static void Resolve(IObjectContainer container)        {            var grade1 = container.Resolve<Grade1>();            Debug.Assert(grade1 == grade1.Room.Grade);            var room1 = container.Resolve<Room2>();            Debug.Assert(room1 == room1.Grade.Room);            // No cyclic dependency in constructor            var grade2 = container.Resolve<Grade2>();            Debug.Assert(grade2 == grade2.Room.Grade);            try            {                // Cyclic dependency in constructor                // The call path is: Grade => Room => Grade                var grade3 = container.Resolve<Grade3>();            }            catch (Exception ex)            {                Debug.Assert(ex is CyclicDependencyException);            }        }    }}
View Code

在範例程式碼中,我們設計了三對 Grade/Room 類,用來示範三種不同的情況。

先來看 Grade1/Room1。

public class Grade1{    public Room1 Room { get; set; }}public class Room1{    public Grade1 Grade { get; set; }}

我們看到這兩個類的屬性相互引用對方,但它們都沒有定義建構函式。這裡,我們以 Grade1 為例來分析一下它們的構造過程。

當我們需要一個 Grade1 對象時,容器首先調用其建構函式為我們建立一個 Grade1 對象。建立好 Grade1 對象之後,我們看到它需要注入一個 Room1 類型的屬性。這樣容器又會為我們建立一個 Room1 對象。隨後,我們看到 Room1 也需要注入一個 Grade1 類型的屬性。此時,由於之前已經建立好一個 Grade1 對象,因此容器在這裡便會複用該 Grade1 對象(這一點很關鍵),以完成 Room1 對象的建立。隨著 Room1 對象建立完成,容器跟著將該對象注入到 Grade1 的 Room 屬性中,這樣 Grade1 也建立完成了。因此,這裡不會導致循環相依性,所以我們運行下面的代碼不會導致異常:

var grade1 = container.Resolve<Grade1>();Debug.Assert(grade1 == grade1.Room.Grade);var room1 = container.Resolve<Room2>();Debug.Assert(room1 == room1.Grade.Room);

接下來,我們看 Grade2/Room2。

public class Grade2{    private readonly Room2 _room;    public Grade2(Room2 room)    {        _room = room;        room.Grade = this;    }    public Room2 Room    {        get { return _room; }    }}public class Room2{    public Grade2 Grade { get; set; }}

當我們構造 Grade2 對象時,並不會引起循環相依性。因為我們看到 Grade2 雖然在建構函式中需要 Room2,但在構造 Room2 的過程中並不需要 Grade2(因為 Room2 沒有定義建構函式,而且也不需要在自身的構造過程中注入 Grade2 這個屬性),所以不會有循環相依性問題。

最後我們來看 Grade3/Room3。

public class Grade3{    private readonly Room3 _room;    public Grade3(Room3 room)    {        _room = room;    }    public Room3 Room    {        get { return _room; }    }}public class Room3{    public Grade3 Grade { get; set; }}

我們看到 Grade3 在建構函式中需要一個 Room3 型別參數,而 Room3 類型雖然沒有定義建構函式,但它在構造過程中要求注入一個 Grade3 類型的屬性值。這樣就引起了問題。因為當 Grade3 需要一個 Room3 對象的時候,Room3 對象尚未建立,這樣容器就需要先建立一個 Room3 對象。但容器在建立 Room3 對象的過程中,又需要注入一個 Grade3 對象。然而此時最初的 Grade3 尚未建立完成(其建構函式已經被調用,但尚未完成),還無法在此處複用。因此,容器又要再建立一個新的 Grade3 對象以滿足 Room3 的構造需要。隨後,這個新的 Grade3 又需要一個新的 Room3,而後面這個新的 Room3 又需要一個新的 Grade3...這樣永無休止地糾纏下去,這個構造過程永遠不會完成。如果用比較簡潔的方式來表述的話,上述建立過程的依賴路徑如下:Grade3 => Room3 => Grade3 => Room3 => Grade3....

這樣我們就明白了,只要我們在設計類時,注意避免產生如上所述的依賴路徑,就能夠有效地防止循環相依性的問題。其實,也就是遵循上面那個原則:[不要在物件建構過程中引入循環相依性]。

 

本文範例程式碼以及 My.Ioc 架構源碼可在此處擷取。

My.Ioc 程式碼範例——避免循環相依性

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.