洩露的封裝
抽象通過公有接口(方法)暴露或洩露實作細節時,将導緻這種壞味。需要注意的是,即使抽象不存在“不充分的封裝”壞味,其公有接口也有可能洩露實作細節。
為什麼不能洩露封裝?
為實作有效封裝,必須将抽象的接口(即抽象的内容)和實作(即抽象的方式)分離。為遵循隐藏原則,必須對客戶程式隐藏抽象的實作方面。
如果通過公有接口暴露了實作細節(違反了隐藏原則)可能會造成:
- 對實作進行修改時,可能會影響客戶程式
- 暴露的實作細節可能會讓客戶程式能夠通過公有接口通路内部資料結構,進而有意或無意地損壞抽象的内部狀态。
##洩露的封裝的潛在原因
不知道該隐藏哪些東西
開發人員通常會在無意之間洩露實作細節。
使用細粒度接口
類的公有接口直接提供了細粒度的方法,這些細粒度的方法通常會向客戶程式暴露不必要的實作細節。更好的做法是在類的公有接口提供粗粒度的方法,在粗粒度方法内部使用細粒度的私有方法。
示例分析一
我們用程式來維護一個待辦事項清單。在ToDoList類中,公有方法GetListEntries()傳回對象存儲的待辦事項清單。
public class ToDoList
{
private List<string> listEntries = new List<string>();
public List<string> GetListEntries()
{
return listEntries;
}
public void AddListEntry(string entry)
{
}
}
複制
問題出在方法的傳回類型上,它暴露了一個内部細節,ToDoList内部使用List來存儲待辦事項清單。
現在問題來了,如果待辦事項清單程式主要執行插入和删除操作,那麼選擇使用List沒啥問題;但是後來發現查找頻率比修改頻率高,那麼使用HashTable可能更合适。然而GetListEntries()的傳回類型是List,如果修改這個方法的傳回類型,可能破壞依賴于這個方法的客戶程式。如果要支援未來資料結構的變更,方法傳回類型可以使用IEnumerable(C#中的集合類型都實作的接口類型),這樣可以做到在不改變方法簽名的條件下(裡氏替換原則),替換存儲待辦事項清單的資料結構。
重構後的代碼實作:
使用List資料結構:
public class ToDoList
{
private List<string> listEntries = new List<string>();
public IEnumerable GetListEntries()
{
return listEntries;
}
public void AddListEntry(string entry)
{
}
}
複制
使用Hashtable資料結構:
public class ToDoList
{
private Hashtable listEntries = new Hashtable();
public IEnumerable GetListEntries()
{
return listEntries;
}
public void AddListEntry(string entry)
{
}
}
複制
方法GetListEntries()存在另一個嚴重的問題是,它傳回一個指向内部資料結構的引用,通過這個引用,客戶程式可以繞過AddListEntry()方法直接修改資料結構。當然如果使用IEnumerable這個問題也就迎刃而解了,因為IEnumerable接口沒有相應的針對于某一種資料集合的操作。
public interface IEnumerable
{
//
// 摘要:
// 傳回循環通路集合的枚舉數。
//
// 傳回結果:
// 一個可用于循環通路集合的 System.Collections.IEnumerator 對象。
[DispId(-4)]
IEnumerator GetEnumerator();
}
複制
示例分析二
假設顯式圖像包含4個步驟,這些步驟必須按照特定順序執行,圖形才可以正常顯式。
現在在Image類中提供4個公有方法Load(),Process(),Validate(),Show()供客戶程式使用,但是這樣有一個很麻煩的問題是寫客戶程式的開發人員不一定會按照正确順序調用方法使用(永遠不要給客戶選擇的權利)。而且客戶程式隻是想要顯式圖像,我們為什麼要向它們暴露4個内部步驟呢?這就是洩露的封裝的潛在原因——使用細粒度接口。
public class Image
{
public void Load()
{
}
public void Process()
{
}
public void Validate()
{
}
public void Show()
{
}
}
複制
要解決這個問題,可以讓Image類隻向客戶程式暴露一個方法Display(),然後在這個方法内部按照特定順序調用4個步驟方法。
public class Image
{
private void Load()
{
}
private void Process()
{
}
private void Validate()
{
}
private void Show()
{
}
public void Display()
{
Load();
Process();
Validate();
Show();
}
}
複制
總結
- 抽象通過公有接口暴露或洩露了實作細節時,客戶程式可能直接依賴于實作細節嗎,這種直接依賴性使得難以在不破壞既有客戶代碼的情況下對設計進行修改或擴充。
- 抽象洩露了内部資料結構時,抽象的完整性遭到了破壞。增加了代碼運作階段發生問題的可能性。
參考:《軟體設計重構》
-----END-----
喜歡本文的朋友們,歡迎掃一掃下圖關注公衆号撸碼那些事,收看更多精彩内容