試想我們在寫一個游戲引擎,創建如下類:
classscriptmanager{publicvoidAddScript(){/*省略實現*/}publicvoidRemoveScript(){/*省略實現*/}}classEntityManager{publicvoidAddEntity(){/*省略實現*/}publicvoidRemoveEntity(){/*省略實現*/}}classAnimationManager{publicvoidAddAnimationToWorld(){/*省略實現*/}publicvoidRemoveAnimationFromWorld(){/*省略實現*/}}
代碼非常簡單,三個manager類分別控制腳本、實體和動畫。但是我們突然發現,這三個類應該都是單例才合適。按照我們之前在C#中的Singleton中介紹的方法,我們這么改寫一下這三個類。
最簡單的,我們可以這么改
classScriptManager{privatestaticScriptManager_instance=null;publicstaticScriptManagerInstance{get{if(_instance==null){lock(typeof(ScriptManager)){if(_instance==null){_instance=newScriptManager();}}}return_instance;}}publicvoidAddScript(){/*省略實現*/}publicvoidRemoveScript(){/*省略實現*/}privateScriptManager(){/*省略實現*/}//車門焊死,不讓外部調用}classEntityManager{//類似的修改方法}classAnimationManager{//類似的修改方法}staticvoidMain(string[]args){varinstance1=ScriptManager.Instance;varinstance2=ScriptManager.Instance;varresult=instance1==instance2;//true}
看起來沒有什么問題,確實也滿足了可用的要求,但是僅僅可用是不夠的,我們想要更好的解決方案,而且這種修改方法雖然簡單,但如果我們想要修改的類不止這三個,或者,我們想要添加的不僅僅是單例方法,我們需要寫的代碼會成倍增加,所以我們想要更好的解決方案。
很容易就能想到,既然這塊代碼邏輯都是一樣的,我們為什么不把它提煉到父類?像這樣
classSingletonHolder<T>whereT:class{privatestaticT_instance=null;publicstaticTInstance{get{if(_instance==null){lock(typeof(T)){if(_instance==null){_instance=(T)Activator.CreateInstance(typeof(T),true);//調用非公有構造器}}}return_instance;}}}classScriptManager:SingletonHolder<ScriptManager>{//省略}classEntityManager:SingletonHolder<EntityManager>{//省略}classAnimationManager:SingletonHolder<AnimationManager>{//省略}staticvoidMain(string[]args){varScriptManager1=ScriptManager.Instance;varScriptManager2=ScriptManager.Instance;varresult=ScriptManager1==ScriptManager2;//truevarEntityManager1=EntityManager.Instance;varEntityManager2=EntityManager.Instance;result=EntityManager1==EntityManager2;//truevarAnimationManager1=AnimationManager.Instance;varAnimationManager2=AnimationManager.Instance;result=AnimationManager1==AnimationManager2;//true}
確實可以,這樣就算有再多的類需要實現單例,只要讓它們繼承SingletonHolder就可以了,這樣的代碼方便擴展也方便維護,畢竟功能邏輯都在父類里面。
不過仔細想想,這樣的代碼還是有點問題,類繼承意味著子類應該是父類的特化,代表著一種is-a的關系,但是我們這幾個Manager類和SingletonHolder并不是這種關系,它們和SingletonHolder更多像是一種實現契約的關系;如果一定要說is-a,它們應該是引擎模塊(ModuleManager)的一種特化。所以讓它們繼承自SingletonHolder其實不是最好的方法,雖然語法正確、行為正確但是并不是語義正確,作為程序員,我們應該追求盡善盡美。而且未來真有可能會抽象出一個父類ModuleManager,到時候就發現唯一的類繼承名額已經給SingletonHolder給占用了,所以我們需要尋找一種既能注入邏輯代碼,又不涉及類繼承的方法。
In object-oriented programming languages, a mixin (or mix-in) is a class that contains methods for use by other classes without having to be the parent class of those other classes. How those other classes gain access to the mixin's methods depends on the language. Mixins are sometimes described as being "included" rather than "inherited".
Mixins encourage code reuse and can be used to avoid the inheritance ambiguity that multiple inheritance can cause (the "diamond problem"), or to work around lack of support for multiple inheritance in a language. A mixin can also be viewed as an interface with implemented methods. This pattern is an example of enforcing the dependency inversion principle.
這是在Wiki上面Mixin的定義,允許程序員以在類繼承之外的方式為類添加一些方法,即,既能為類提供方法實現,又可以避免成為類的父類,避免了類繼承和多重繼承所帶來的問題,這種概念正是我們需要的。
在C#中,它們通常以擁有實現的接口出現(default implementation interface from C#8.0),而在C#8.0之前,我們通常以輔助類的方式來實現Mixin,我們下面以這兩種方式改寫之前的類。
我們定義出一個接口,然后在外部基于這個接口實現單例邏輯(不用擴展方法是因為擴展方法不支持static method,如果想要注入的是非static method可以使用基于接口的擴展方法)
classSingletonHolder<T>whereT:class,ISingleton{privatestaticT_instance=null;publicstaticTInstance{get{if(_instance==null){lock(typeof(T)){if(_instance==null){_instance=(T)Activator.CreateInstance(typeof(T),true);}}}return_instance;}}}interfaceISingleton{//沒有任何方法因為只是一個標記}classScriptManager:ISingleton{privateScriptManager(){/*省略實現*/}publicvoidAddScript(){/*省略實現*/}publicvoidRemoveScript(){/*省略實現*/}}classEntityManager:ISingleton{privateEntityManager(){/*省略實現*/}publicvoidAddEntity(){/*省略實現*/}publicvoidRemoveEntity(){/*省略實現*/}}classAnimationManager:ISingleton{privateAnimationManager(){/*省略實現*/}publicvoidAddAnimationToWorld(){/*省略實現*/}publicvoidRemoveAnimationFromWorld(){/*省略實現*/}}staticvoidMain(string[]args){varScriptManager1=SingletonHolder<ScriptManager>.Instance;varScriptManager2=SingletonHolder<ScriptManager>.Instance;varresult=ScriptManager1==ScriptManager2;//truevarEntityManager1=SingletonHolder<EntityManager>.Instance;varEntityManager2=SingletonHolder<EntityManager>.Instance;result=EntityManager1==EntityManager2;//truevarAnimationManager1=SingletonHolder<AnimationManager>.Instance;varAnimationManager2=SingletonHolder<AnimationManager>.Instance;result=AnimationManager1==AnimationManager2;//true}
這就是Mixin的用處,看起來這種實現方式的好處有:
類只需要聲明實現ISingleton即可完成單例相關編碼
ISingleton是接口,類可以聲明實現多個接口而不會有類繼承的單一限制,同時也不會有那種is-a的類繼承煩惱
ISingleton是空接口,任何類實現它不需要額外的對該類自身的修改,就像淋上草莓醬不會對冰淇淋本身造成影響一樣,符合開閉原則
從C#8.0開始,接口可以有方法的默認實現(包括static method),我們可以更加簡單的實現Mixin解決之前的問題
interfaceSingletonHolder<T>whereT:class{privatestaticT_instance=null;staticTInstance{get{if(_instance==null){lock(typeof(T)){if(_instance==null){_instance=(T)Activator.CreateInstance(typeof(T),true);}}}return_instance;}}}classScriptManager:SingletonHolder<ScriptManager>{}classEntityManager:SingletonHolder<EntityManager>{}classAnimationManager:SingletonHolder<AnimationManager>{}
本文由 貴州做網站公司 整理發布,部分圖文來源于互聯網,如有侵權,請聯系我們刪除,謝謝!
c語言中正確的字符常量是用一對單引號將一個字符括起表示合法的字符常量。例如‘a’。數值包括整型、浮點型。整型可用十進制,八進制,十六進制。八進制前面要加0,后面...
2022年天津專場考試原定于3月19日舉行,受疫情影響確定延期,但目前延期后的考試時間推遲。 符合報名條件的考生,須在規定時間登錄招考資訊網(www.zha...
:喜歡聽,樂意看。指很受歡迎?!巴卣官Y料”喜聞樂見:[ xǐ wén lè jiàn ]詳細解釋1. 【解釋】:喜歡聽,樂意看。指很受歡迎。2. 【示例】:這是...
現貨倉單是什么?現貨倉單是一種憑證,表示倉單中規定的標準轉運,可以在指定倉庫購買或出售?,F貨倉單以企業一定保證金的形式進行交易?,F貨倉單交易和期貨交易很像。它不僅是一種商品交易手段,也是一種金融投資手段。他和期貨在定義上的區別在于,目標商品在指定倉庫的交割時間可以是持有現貨倉單到現貨倉單最后一個交易日的一段時間。期貨合約對目標商品有明確的交割日期。如何控制期貨投資者的風險?作為期貨市場的投資者,尤...
我國資本市場什么時候開始的?中國資本市場是隨著1990年深滬交易所的成立,開始正式出現的。1990年, 上海, 深圳兩家證券交易所正式開放營業。在資本市場創立了的開始,不少的人還在議論和猶豫。直到1992年1月份 “鄧小平的講話”,才讓資本市場迅速開始發展。資本市場包括哪些?資本市場市場包括:股票、債券和基金業務。1.股票它是股份公司發行的所有權憑證,是股份公司向每一位股東...
大量股票解禁反而暴漲咋回事?股票跌漲不由解禁決定,且股票公開不代表控股股東立刻減持,股票跌漲由供求關系求、資金額、銷售績業、政策、消息等各個方面因素決定。股票解禁代表著大量的非流通股可以進行流通,減持需要提早傳出公示,非流通股占總股本5%以上的,一般需 要2年以上才可以減持,非流通股占總股本小于5%的,一般規定在一年之后才可以減持。解禁股票當天一般漲還是跌?跌,股票解禁后第一天一般是下跌,下跌的概...