C#类中的internal成员可能是一种坏味道
前言
最近除了搞ASP.NET MVC之外,我也在思考一些編程實踐方面的問題。昨天在回家路上,我忽然對一個問題產生了較為清晰的認識。或者說,原先只是有一絲細微的感覺,而現在將它和一些其他的方面進行了聯系,也顯得頗為“完備”。這就是問題便是:如何對待類中internal成員。我現在認為“類中的internal成員可能是一個壞味道”,換句話說,如果您的類中出現了internal的成員,就可能是設計上的問題了。
可能這個命題說得還有些籠統,所以再詳細地描述一下比較妥當。我的意思是,您的類庫中出現internal的類型是完全沒有問題的(也肯定是無法避免的)。然而,一個經過良好設計的類型,是應該很少出現internal的方法或屬性的(字段就不在考慮范圍,因為它應該永遠是私有的)。其中有例外,如“構造函數”的修飾級別,稍后會再談到。
C#中一個類中的成員有四種修飾級別:
- public:完全開放,誰都能訪問。
- private:完全封閉,只有類自身可以訪問。
- internal:只對相同程序集,或使用InternalVisibleToAttribute標記的程序集開放。
- protected:只對子類開放。
您也可以將protected和internal修飾同一個成員,這使得類中的一個成員可以擁有5種不同的訪問權限。我認為,其中pubic、private和protected級別的含義是清晰而純粹的,而internal的開放程度則是像是一個“灰色地帶”。
Internal類中的Internal成員
我們為什么會使用internal修飾符?最簡單的答案,自然是為了讓相同程序集內類型可以訪問,但是不對外部開放。那么我們什么時候會用這種訪問級別呢?可能是這樣的:
<span style="color:#333333"><span style="color:red">internal</span> <span style="color:blue">class </span><span style="color:#2b91af">SomeClass </span>{<span style="color:blue">internal void </span>SomeMethod() { } } </span>請注意,這里我們在一個internal的類型中使用了internal來修飾這個方法。這是一種累贅,因為它和public修飾效果完全一致,這會造成不清晰的修飾性(灰色地帶)。因此,在internal類型中,所有的成員只能是public、private和protected訪問級別。也就是說,上面的代碼應該改成:
<span style="color:#333333"><span style="color:blue">internal class </span><span style="color:#2b91af">SomeClass </span>{<span style="color:red">public</span> <span style="color:blue">void </span>SomeMethod() { } } </span>于是,內部類中哪些是私有的,哪些是公開的(可以被相同程序集內訪問到)一目了然。這個類的職責也非常明確。
Public類的Internal成員
這個問題就麻煩了許多,因為此時類中的internal成員含義就非常明確了:
<span style="color:#333333"><span style="color:blue">public class </span><span style="color:#2b91af">SomeClass </span>{<span style="color:blue"><span style="color:red">internal</span> void </span>SomeMethod() { } } </span>public類中的internal成員可以被相同程序集內的類型訪問到,而對外部的程序集是隱藏的。這意味著,這個類的功能分了兩部分,一部分對所有人公開,還有一部分對自己人公開,對其他人關閉。在很多時候,這可能意味著一個類擁有了兩種職責,一種對外,一種對內,而這種情況顯然違背了“單一職責原則”。這時候我們可能需要重構,把一部分對內的職責封裝為額外的internal類型,并負責內部邏輯的交互。如此,代碼可能就會寫成這樣:
<span style="color:#333333"><span style="color:blue">internal class </span><span style="color:#2b91af">InternalClass </span>{<span style="color:blue">private </span><span style="color:#2b91af">SomeClass</span> m_someClass;<span style="color:blue">public </span>InternalClass(<span style="color:#2b91af">SomeClass</span> someClass){<span style="color:blue">this</span>.m_someClass = someClass;}<span style="color:blue">public void </span>SomeMethod(){<span style="color:green">/* use data on this.m_someClass. */</span>} }<span style="color:blue">public class </span><span style="color:#2b91af">SomeClass </span>{<span style="color:green">// public members </span>} </span>不過這可能也是最容易產生爭議的地方,因為這“削減”了internal的相當一大部分作用,此外還會造成代碼的增加。而事實上,很多時候也應該在public類中使用internal方法,只要不違背“單一職責原則”即可。不過我想,這方面的“權衡”應該也是較為容易的,因為基本上所有的考量都是基于“職責”的。
這也是我思考中經常遇到的問題,就是某種“實踐”是不是屬于“過度設計”了。我們的目標是快速發布,確保質量,而不是為了遵循原則而去遵循原則。在今后此類文章中,我也會提出類似的“權衡”,如果您有看法,歡迎和我交流。
為了單元測試而使用Internal成員
例如,一個類中有一個復雜的私有方法,我們希望對它進行單元測試。由于private成員無法被外部訪問,因此我們會將其寫成internal的方法:
<span style="color:#333333"><span style="color:blue">public class </span><span style="color:#2b91af">SomeClass </span>{<span style="color:blue">public void </span>SomeMethod(){ <span style="color:green">// do something...</span><span style="color:blue">this</span>.ComplexMethod();<span style="color:green">// do something else...</span>}<span style="color:blue">internal void </span>ComplexMethod() { } } </span>由于是internal方法,我們可以使用InternalVisibleToAttribute釋放給其他程序集,就可以在那個程序集中編寫單元測試代碼。但是我認為這個做法不好。
首先,我一直不喜歡為了“單元測試”而改變原有的封裝性,即使改成internal成員后,對其他外部程序集來說并沒有什么影響。 在MSDN Web Cast或其他一些地方,我可能講過我們“可以”把private方法改為internal,僅僅是為單元測試。還有便是把protected也改成protected internal——我也會寫文章討論這個問題。
其實這又涉及到是否應該測試私有方法的問題,我最近會再對此進行較為詳細的討論。如果您有一個需要測試的復雜的私有方法,這意味著這個私有方法可能會有獨立的職責,獨立的算法。我們又值得將其獨立提取出來:
<span style="color:#333333"><span style="color:blue">internal class </span><span style="color:#2b91af">ComplexClass </span>{<span style="color:blue">public void </span>ComplexMethod() { } }<span style="color:blue">public class </span><span style="color:#2b91af">SomeClass </span>{<span style="color:blue">private </span><span style="color:#2b91af">ComplexClass </span>m_complexClass = <span style="color:blue">new </span><span style="color:#2b91af">ComplexClass</span>();<span style="color:blue">public void </span>SomeMethod(){ <span style="color:green">// do something...</span><span style="color:blue">this</span>.m_complexClass.ComplexMethod();<span style="color:green">// do something else...</span>} } </span>由于ComplexClass是internal的,我們便可以為其進行獨立的單元測試。
一些例外情況
萬事都有例外。例如對于構造函數來說,internal在很多時候是一個“必須”的修飾符:
<span style="color:#333333"><span style="color:blue">internal class </span><span style="color:#2b91af">ComplexClass </span>{<span style="color:blue">public virtual void </span>ComplexMethod() { } }<span style="color:blue">public class </span><span style="color:#2b91af">SomeClass </span>{<span style="color:blue">private </span><span style="color:#2b91af">ComplexClass </span>m_complexClass;<span style="color:blue">public </span>SomeClass(): <span style="color:blue">this</span>(<span style="color:blue">new </span><span style="color:#2b91af">ComplexClass</span>()){ }<span style="color:blue"><span style="color:red">internal</span> </span>SomeClass(<span style="color:#2b91af">ComplexClass </span>complexClass){<span style="color:blue">this</span>.m_complexClass = complexClass;}<span style="color:blue">public void </span>SomeMethod(){ <span style="color:green">// do something...</span><span style="color:blue">this</span>.m_complexClass.ComplexMethod();<span style="color:green">// do something else...</span>} } </span>由于其中一個構造函數是internal的,并接受一個對象,因此單元測試便可以利用這個構造函數“注入”一個對象(往往是一個Mock對象)。而對外公開的構造函數,便可以直接提供一個具體的實例,作為真實場景中的使用方式。
from:http://blog.zhaojie.me/2009/08/internal-member-is-bad-smell.html?
總結
以上是生活随笔為你收集整理的C#类中的internal成员可能是一种坏味道的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入浅出OOP(五): C#访问修饰符(
- 下一篇: 红黑树详细分析