Effective C# 避免返回内部类对象的引用

大家可能认为只读属性就只能读取,调用者不可能更改属性值。可惜的是,并非所有情况都如此。如果我们创建的属性返回了一个引用类型,那么调用者就可以访问该对象的公有成员,包括那些修改属性状态的成员。例如:

 

public   class  MyBusinessObject

ExpandedBlockStart.gifContractedBlock.gif
{

// 只读属性提供了对私有数据成员的访问:

private DataSet _ds;

public DataSet Data

ExpandedSubBlockStart.gifContractedSubBlock.gif
{

    
get

ExpandedSubBlockStart.gifContractedSubBlock.gif    
{

      
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

ExpandedBlockStart.gifContractedBlock.gif
{

// 只读属性提供了对私有数据成员的访问:

private DataSet _ds;

public DataView thisstring tableName ]

ExpandedSubBlockStart.gifContractedSubBlock.gif
{

    
get

ExpandedSubBlockStart.gifContractedSubBlock.gif    
{

      
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

ExpandedBlockStart.gifContractedBlock.gif
{

// 只读属性提供了对私有数据成员的访问:

private DataSet _ds;

public IList thisstring tableName ]

ExpandedSubBlockStart.gifContractedSubBlock.gif
{

    
get

ExpandedSubBlockStart.gifContractedSubBlock.gif    
{

      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(观察者)模式,以使对象可以对更改进行校验或响应。

转载于:https://www.cnblogs.com/VisualStudio/archive/2008/10/28/1321572.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值