並不是所有的人都須要知道所有的事。也不是所有的類型須要是公用的。對於每個類型,在滿足功能的情況下,應該儘可能的限制存取層級。而且這些存取層級往往比你想像的要少得多。在一個私人類型上,所有的使用者都可以通過一個公用的介面來訪問這個介面所定義的功能。
讓我們回到最根本的情況上來:強大的工具和懶惰的開發人員。VS.net對於他們來說是一個偉大的高產工具。我用VS.net或者C# Builder輕鬆的開發我所有的項目,因為它讓我更快的完成任務。其中一個加強的高產工具就是讓你只用點兩下按鈕,一個類就建立了,當然如果這正是我想要的話。VS.net為我們建立的類就是這樣的:
public class Class2
{
public Class2()
{
//
// TODO: Add constructor logic here
//
}
}
這是一個公用類,它在每個使用我的程式集的代碼塊上都是可見的。這樣的可見層級太高了,很多獨立存在的類都應該是內部(internal)的。你可以通過在已經存在的類裡嵌套一個受保護的或者私人的類來限制訪問。 越低的存取層級,對於今後的更新整個系統的可能性就越少。越少的地方可以訪問到類型,在更新時就越少的地方要修改。
只暴露須要暴露的內容,應該通過嘗試在類上實現公用介面來減少可見內容。你應該可以在.Net架構庫裡發現使用Enumerator模式的例子,System.ArrayList包含一個私人類,ArrayListEnumerator, 而就是它只實現了IEnumerator介面:
// Example, not complete source
public class ArrayList: IEnumerable
{
private class ArraylistEnumerator : IEnumerator
{
// Contains specific implementation of
// MoveNext( ), Reset( ), and Current.
}
public IEnumerator GetEnumerator()
{
return new ArrayListEnumerator( this );
}
// other ArrayList members.
}
對於我們這樣的使用者來說,不須要知道ArrayListEnumerator類,所有你須要知道的,就是當我們在ArrayList對象上調用GetEnumerator函數時,你所得到的是一個實現了IEnumerator介面的對象。而具體的實現則是一個明確的類。.Net架構的設計者在另一個集合類中使用了同樣的模式:雜湊表(Hashtable)包含一個私人的HashtableEnumerator, 隊列(Queue)包含一個QueueEnumerator, 等等。私人的枚舉類有更多的優勢。首先,ArrayList類可以完全取代實現IEnumerator的類型,而且你已經成為一個賢明的程式員了,不破壞任何內容。其實,列舉程式類不須要是CLS相容的,因為它並不是公用的(參見條款30)。而它的公用介面是相容的。你可以使用列舉程式而不用知道實現的類的任何細節問題。
建立內部的類是經常使用的用於限制類型可見範圍的概括方法。預設情況下,很多程式員都總是建立公用的類,從來不考慮其它方法。這是VS.net的事。我們應該取代這種不加思考的預設,我們應該仔細考慮你的類型會在哪些地方使用。它是所有使用者可見的?或者它主要只是在一個程式集內部使用?
通過使用介面來暴露功能,可以讓你更簡單的建立內部類,而不用限制它們在程式集外的使用(參見條款19)。類型應該是公用的呢?或者有更好的介面彙總來描述它的功能?內部類可以讓你用不同的版本來替換一個類,只要在它們實現了同樣的介面時。做為一個例子,考慮這個電話號碼驗證的問題:
public class PhoneValidator
{
public bool ValidateNumber( PhoneNumber ph )
{
// perform validation
// Check for valid area code, exchange
return true;
}
}
幾個月過後,這個類還是可以很好的工作。當你得到一個國際電話號碼的請求時,前面的這個PhoneValidator就失敗了。它只是針對US的電話號碼的。你仍然要對US電話號碼進行驗證,而現在,在安裝過程中還要對國際電話號碼進行驗證。與其粘貼額外的功能代碼到一個類中,還不如了斷減少兩個不同內容耦合的做法,直接建立一個介面來驗證電話號碼:
public interface IPhoneValidator
{
bool ValidateNumber( PhoneNumber ph );
}
下一步,修改已經存在的電話驗證,通過介面來實現,而且把它做為一個內部類:
internal class USPhoneValidator : IPhoneValidator
{
public bool ValidateNumber( PhoneNumber ph )
{
// perform validation.
// Check for valid area code, exchange.
return true;
}
}
最後,你可以為國際電話號碼的驗證建立一個類:
internal class InternationalPhoneValidator : IPhoneValidator
{
public bool ValidateNumber( PhoneNumber ph )
{
// perform validation.
// Check international code.
// Check specific phone number rules.
return true;
}
}
為了完成這個實現,你須要建立一個恰當的類,這個類基於電話號碼類型類,你可以使用類廠模式實現這個想法。在程式集外,只有介面是可見的。而實際的類,就是這個為世界不同地區使用的特殊類,只有在程式集內是可見的。你可以為不同的地區的驗證建立不同的驗證類,而不用再系統裡的其它程式集而煩擾了。
你還可以為PhoneValidator建立一個公用的抽象類別,它包含通用驗證的實現演算法。使用者應該可以通過程式集的基類訪問公用的功能。在這個例子中,我更喜歡用公用介面,因為即使是同樣的功能,這個相對少一些。其他人可能更喜歡抽象類別。不管用哪個方法實現,在程式集中儘可能少的公開類。
這些暴露在外的公用類和介面就是你的合約:你必須保留它們。越多混亂的介面暴露在外,將來你就越是多的直接受到限制。越少的公用類型暴露在外,將來就越是有更多的選擇來擴充或者修改任何的實現。