你已經知道,所謂的只讀屬性就是指調用者無法修改這個屬性。不幸運的是 ,這並不是一直有效的。如果你創建了一個屬性,它返回一個引用類型,那麼調 用者就可以訪問這個對象的公共成員,也包括修改這些屬性的狀態。例如:
public class MyBusinessObject
{
// Read Only property providing access to a
// private data member:
private DataSet _ds;
public DataSet Data
{
get
{
return _ds;
}
}
}
// Access the dataset:
DataSet ds = bizObj.Data;
// Not intended, but allowed:
ds.Tables.Clear( ); // Deletes all data tables.
任何MyBusinessObject的公共客戶都可以修改你的內部 dateset。你創建的屬性用來隱藏類的內部數據結構,你提供了方法,讓知道該 方法的客戶熟練的操作數據。因此,你的類可以管理內部狀態的任何改變。然而 ,只讀屬性對於類的封裝來說開了一個後門。當你考慮這些問題時,它並不是一 個可讀可寫屬性,而是一個只讀屬性。
歡迎來到一個精彩的基於引用的 系統,任何返回引用的成員都會返回一個對象的句柄。你給了調用者一個接口的 句柄,因此調用者修改這個對象的某個內部引用時,不再需要通過這個對象。
很清楚,你想防止這樣的事情發生。你為你的類創建了一個接口,同時 希望用戶使用這個接口。你不希望用戶在不明白你的意圖時,訪問並修改對象的 內部狀態。你有四個策略來保護你的內部數據結構不被無意的修改:值類型,恆 定類型,接口和包裝(模式)。
值類型在通過屬性訪問時,是數據的拷貝 。客戶對類的拷貝數據所做的任何修改,不會影響到對象的內部狀態。客戶可以 根據需求隨意的修改拷貝的數據。這對你的內部狀態沒有任意影響。
恆 定類型,例如System.String,也是安全的。你可以返回一個字符串,或者其它 恆定類型。恆定類型的安全性告訴你,沒有客戶可以修改字符串。你的內部狀態 是安全的。
第三個選擇就是定義接口,從而充許客戶訪問內部成員的部 份功能(參見原則19)。當你創建一個自己的類時,你可以創建一些設置接口,用 來支持對類的子對象進行設置。通過這些接口來暴露一些功能函數,你可以盡可 能的減少一些對數據的無意修改。客戶可以通過你提供的接口訪問類的內部對象 ,而這個接口並不包含這個類的全部的功能。在DataSet上暴露一個IListsource 接口就是這種策略,可以阻止一些有想法的程序員來猜測實現這個接口的對象, 以及強制轉換。這樣做和程序員付出更多的工作以及發現更多的BUG都是自找的( 譯注:這一句理解可能完全不對,讀者可以自行參考原文:But programmers who go to that much work to create bugs get what they deserve.)。
System.Dataset類同時也使用了最後一種策略:包裝對象。 DataViewManager類提供了一種訪問DataSet的方法,而且防止變向的方法來訪問 DataSeto類:
public class MyBusinessObject
{
// Read Only property providing access to a
// private data member:
private DataSet _ds;
public DataView this[ string tableName ]
{
get
{
return _ds.DefaultViewManager.
CreateDataView( _ds.Tables[ tableName ] );
}
}
}
// Access the dataset:
DataView list = bizObj[ "customers" ];
foreach ( DataRowView r in list )
Console.WriteLine( r[ "name" ] );
DataViewManager創建DataView來訪問 DataSet裡的個別數據表。DataViewManager沒有提供任何方法來修改DataSet裡 的數據表。每一個DataView可以被配置為許可修改個別數據元素,但客戶不能修 改數據表,或者數據表的列。讀/寫是默認的,因此客戶還是可以添加,修改, 或者刪除個別的數據條目。
在我們開始討論如何創建一個完全只讀的數 據視圖時以前,讓我先簡單的了解一下你應該如何響應公共用戶的修改。這是很 重要的,因為你可能經常要暴露一個DataView給UI控件,這樣用戶就可以編輯數 據(參見原則38)。確信你已經使用過Windows表單的數據綁定,用來給用戶提供 對象私有數據編輯。DataSet裡的DataTable引發一些事件,這樣就可以很容易的 實現觀查者模式:你的類可以響應其它客戶的任何修改。DataSet裡的DataTable 對象會在數據表的任何列以及行發生改變時引發事件。ColumnChanging和 RowChanging事件會在編輯的數據提交到DataSet前被引發。而ColumnChanged和 RowChanged事件則是在修改提交後引發。
任何時候,當你期望給公共客 戶提供修改內部數據的方法時,都可以擴展這樣的技術,但你要驗證而且響應這 些改變。你的類應該對內部數據結構產生的事件做一些描述。事件句柄通過更新 這些內部的狀態來驗證和響應改變。
回到原來的問題上,你想讓客戶查 看你的數據,但不許做任何的修改。當你的數據存儲在一個DataSet裡時,你可 以通過強制在DataTable上創建一個DataView來防止任何的修改。DataView類包 含一些屬性,通過定義這些屬性,可以讓DataView支持在實際的表上添加,刪除 ,修改甚至是排序。你可以在被請求的DataTable上使用索引器,通過創建一個 索引器來返回一個自定義的DataView:
public class MyBusinessObject
{
// Read Only property providing access to a
// private data member:
private DataSet _ds;
public IList this[ string tableName ]
{
get
{
DataView view =
_ds.DefaultViewManager.CreateDataView
( _ds.Tables[ tableName ] );
view.AllowNew = false;
view.AllowDelete = false;
view.AllowEdit = false;
return view;
}
}
}
// Access the dataset:
IList dv = bizOjb[ "customers" ];
foreach ( DataRowView r in dv )
Console.WriteLine( r[ "name" ] );
這個類的最後一點摘錄(的代碼)通過訪 問IList接口引用,返回這個實際數據表上的視圖。你可以在任何的集合上使用 IList接口,並不僅限於DataSet。你不應該只是簡單的返回DataView對象。用戶 可以再次簡單的取得編輯,添加/刪除的能力。你返回的視圖已經是自定義的, 它不許可在列表的對象上做任何的修改。返回的IList指針確保客戶沒有像 DataView對象裡賦於的修改權利。
從公共接口上暴露給用戶的引用類型 ,可以讓用戶修改對象內部成員,而不用訪問該對象。這看上去不可思議,也會 產生一些錯誤。你須要修改類的接口,重新考慮你所暴露的是引用而不是值類型 。如果你只是簡單的返回內部數據,你就給了別人機會去訪問內部成員。你的客 戶可以調用成員上任何可用的方法。你可以通過暴露接口來限制一些內部私有數 據訪問,或者包裝對象。當你希望你的客戶可以修改你的內部數據時,你應該實 現你自己的觀察者模式,這樣你的對象可以驗證修改或者響應它們。
返回教程目錄