屬性返回引用暴露內部封裝

本文引用自:http://www.cnblogs.com/VisualStudio/archive/2008/10/28/1321572.html


大家可能認爲只讀屬性就只能讀取,調用者不可能更改屬性值。可惜的是,並非所有情況都如此。如果我們創建的屬性返回了一個引用類型,那麼調用者就可以訪問該對象的公有成員,包括那些修改屬性狀態的成員。例如:

 

public class MyBusinessObject

{

// 只讀屬性提供了對私有數據成員的訪問:

private DataSet _ds;

public DataSet Data

{

    
get

    
{

      
return _ds;

    }


}


}


// 訪問DataSet:

DataSet ds 
= bizObj.Data;

// 並非我們期望的行爲,但是這麼做是允許的:

ds.Tables.Clear( ); 
// 刪除所有數據表。

 

 

這裏,任何外部的客戶代碼都可以修改MyBusinessObject類型內部的DataSet。我們可以創建屬性來隱藏內部的數據結構,也可以創建方法來讓客戶代碼僅通過它們操作數據,這樣我們的類就可以管理對內部狀態的任何改變。但是一個只讀屬性卻將這樣的類封裝打開了一個缺口。由於是隻讀屬性,而不是一個讀/ 寫屬性,因此會出現我們考慮不到的問題。

歡迎大家來到奇妙的基於“引用”的類型系統中來!在這樣的系統中,任何返回引用類型的成員,都會返回該對象的一個句柄(handle)。這個句柄使得調用者可以到達對象內部的數據結構,無需通過對象就可以改變其中包含的引用。

顯然,我們希望避免這種行爲。我們可以選擇爲類創建接口,然後讓用戶通過接口使用對象。我們不希望用戶在我們不知道的情況下,訪問或者改變對象的內部狀態。共有4種不同的策略可以防止類型的內部數據結構遭受無意的改變:值類型、常量類型、接口和包裝器(wrapper)。

當客戶代碼通過屬性來訪問值類型成員時,實際返回的是值類型的副本。對該副本的任何更改都不會影響對象的內部狀態。客戶代碼可以根據自己的需要更改該副本,以達到它們的目的這不會影響內部狀態。

常量類型,如System.String,也是安全的。我們可以在類型中安全地返回string或者其他常量類型,客戶代碼不可能對它們做任何更改,因此可以確保類型內部狀態的安全。

第3種選擇是通過定義接口,將客戶對內部數據成員的訪問限制在一個子集中(參見條款19)。當我們創建類時,可以創建一組接口來支持類型功能的子集。通過使用接口向外界提供類型的功能,我們可以將內部數據遭受無意更改的可能性最小化。客戶代碼可以通過我們提供的接口(不包括類型的全部功能)訪問內部對象。例如,使用IListSource接口向外提供 DataSet的功能就是這種策略的一個應用。某些“詭計多端”的程序員可能會通過猜測實現接口的對象類型,然後使用強制轉型來破壞這種策略。但是這樣的做法肯定會造成一些bug。

最後一種策略:包裝器(wrapper)對象,在System.DataSet類中也有應用。DataViewManager類爲我們提供了訪問DataSet的方式,但它卻阻止我們調用那些DataSet類上的變動性方法:

 

public class MyBusinessObject

{

// 只讀屬性提供了對私有數據成員的訪問:

private DataSet _ds;

public DataView thisstring tableName ]

{

    
get

    
{

      
return _ds.DefaultViewManager.

        CreateDataView( _ds.Tables[ tableName ] );

    }


}


}


// 訪問dataset:

DataView list 
= bizObj[ "customers" ];

foreach ( DataRowView r in list )

Console.WriteLine( r[ 
"name" ] );

 

 

DataViewManager 通過創建一些DataView來訪問DataSet中的各個數據表。這樣,用戶就無法更改DataSet中的表了。我們可以對每個DataView進行配置以支持對單個數據元素的更改。但是客戶代碼無法更改其中的表或者數據列。因爲讀/寫是被默認支持的,所以客戶代碼仍然可以添加、修改或刪除單個的數據條目。

在探討如何創建一個完全只讀的數據視圖之前,先來看看當允許外部客戶代碼更改數據時,我們有什麼樣的辦法可以響應這種更改?這很重要,因爲我們可能經常需要將一個DataView導出到UI控件上,以支持用戶編輯數據。大家肯定都用過Windows Forms的數據綁定功能,它可以幫助用戶編輯對象的私有數據。DataSet中的DataTable類觸發的事件使其可以很容易地實現Observer (觀察者)模式:我們的類可以響應客戶代碼對其所做的任何改變。當DataSet中的DataTable的任何列或者行發生改變時,都會觸發相關的事件。在將一個編輯動作提交給DataTable之前,會有ColumnChanging和RowChanging事件被觸發。在提交改變之後,會有 ColumnChanged和RowChanged事件被觸發。

當希望將內部數據元素暴露給外界,供外部客戶代碼更改時,也可以利用這種技巧,但我們需要對這些更改進行校驗和響應。我們的類可以訂閱那些由內部數據結構產生的事件。然後讓事件處理器通過更新其他內部狀態,來對更改進行校驗和響應。

回到原來的問題上,我們希望允許客戶代碼查看數據,但卻不希望它們做任何更改。當我們的數據存儲在一個DataSet中時,我們可以通過創建一個不允許更改的DataView,來確保這一點。 DataView類中包含的屬性允許我們指定是否支持在特定表上的添加、刪除、更改,甚至排序操作。我們可以創建一個索引器來根據所請求的使用索引器的表,返回一個定製的DataView:

 

public class MyBusinessObject

{

// 只讀屬性提供了對私有數據成員的訪問:

private DataSet _ds;

public IList thisstring tableName ]

{

    
get

    
{

      DataView view 
=

        _ds.DefaultViewManager.CreateDataView

        ( _ds.Tables[ tableName ] );

      view.AllowNew 
= false;

      view.AllowDelete 
= false;

      view.AllowEdit 
= false;

      
return view;

    }


}


}


// 訪問DataSet:

    IList dv 
= bizOjb[ "customers" ];

    
foreach ( DataRowView r in dv )

      Console.WriteLine( r[ 
"name" ] );

 

 

在上面的類中,我們使用IList接口來返回特定數據表的視圖。我們可以在任何集合上使用IList接口,它並不侷限於DataSet。我們不應該簡單地返回 DataView對象,因爲用戶可以很容易地再次啓用它的編輯、增加和刪除能力。通過定製返回的視圖,我們則可以避免對鏈表中的對象的更改。返回 IList接口事實上禁止了用戶改變DataView對象的操作權限。

綜上所述,將引用類型通過公有接口暴露給外界,將使得類型的用戶不用通過我們定義的方法和屬性,就能夠更改對象的內部結構。這違反了我們通常的直覺,會導致常見的錯誤。如果我們導出的是引用而非值,那就需要改變類型的接口。如果只是簡單地返回內部數據,那麼我們實際上就給外界賦予了訪問內部成員的權限。客戶代碼可以調用成員中任何可用的方法。通過使用接口或者包裝器對象向外界提供內部的私有數據,我們可以限制外界對它們的訪問能力。當希望客戶代碼更改內部數據元素時,我們應該實現Observer(觀察者)模式,以使對象可以對更改進行校驗或響應。

發佈了41 篇原創文章 · 獲贊 7 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章