关于.Net中属性的使用探讨(二)
codeprince http://tangyong.net@163.com
三.编写Get过程中需要注意的问题
当定义一个标量属性的时候,如上面所定义的CityName,这是一个既可读又可写的属性,当然你可以将它定义为只读的属性。我们需要考虑如何去编写get过程。(更多的人是和我开始的时候一样,什么都不做,写一个return语句,我要说这是安全的,但我们还需要了解其他一些东西)。我要说的第一个问题是:Get函数不应该有任何明显的副作用,换句话说,你对Get过程的定义不能影响到这个类的对象状态,比如说你编写CityName这个属性,但你又想通过CityName反映城市与城市之间的某种关系,这样就会增加CityName的副作用,这是不好的行为。为什么呢?这是因为调用Get过程在逻辑上与访问字段类似,如果我们发现了自己在Get函数中更新了状态,那么我的建议(也是Andy Olsen等专家的建议)是将这个Get过程实现为方法。方法是可以也是经常会产生副作用的。
也许有人会说既然这样,方法不是可以取代属性了吗,还要它干吗?这本身就是一种替代和包含的关系吗!其实有的时候我也这么想,但我要说有三种情况也许会打动你的心从而使用属性。
1. 要获取类中现有的字段值。在这种情况下,Get函数只作为已经在类中出现的一块数据的包装器。Get函数直接返回私有字段的值。(这也是我们最为经常遇到的)
2. 要计算类中的派生值。在这种情况下,Get过程使用类中的数据来计算新的值,对于面向对象的分析和设计来说,这叫做类的派生特性。
3. 要执行“懒惰”初始化。(也就是推迟初始化类中的私有字段)在这种情况下,类有一个很少使用的字段,或者说有些字段在创建对象时,你无法预计它的值(当然由编译器可以指派一个默认的值),为了效率,我们可以推迟初始化这个字段,直到它通过Set过程(或其他的方法显示地赋值)。当用户需要这个字段时,我们再通过Get过程得到。如果客户应用程序永远不请求这个字段,那么我们也就永远不用计算它,这样就提高了性能。
下面分析一个例子:
Class City
private mCityName as String '城市的名称
private mBuildTime as DateTime '城市的修建时间
private mChargedRegionSquares as String '城市所管辖的地域面积
public Sub New(Byval CityName as String,Byval BuildTime as DateTime)
mCityName=CityName
mBuildTime=CityHistory
mChargedRegionSquares="<Not Read>"
End Sub
public Property CityName() as String
Get
Return mCityName
End Get
Set(Byval Value as String)
mCityName=Value
End Set
End Property
'CityHistory属性代表了城市已存在的时间
Public ReadOnly Property CityHistory() as Integer
Get
Dim Today as DateTime=DateTime.Now
Dim Years as Integer=DateTime.Now.Year-mBuildTime.Year
if (Today.Month<mBuildTime.Month) or
(Today.Month=mBuildTime.Month and
Today.Day<mBuildTime.Day) then
Years-=1
End If
Return Years
End Get
End Property
public ReadOnly Property ChargedRegionSquares() as String
Get
if (mChargedRegion="<Not Read>" then
Dim Conn as OleDbConnection
Dim Cmd as OleDbCommand
Try
Conn=New OleDbConnection("Provider=........."&_
"DataSource=City.mdb")
Cmd=New OleDbCommand("Select RegionSquares From
CityRegions Where CityName='"&mName&"'",
Conn)
mChargedRegionSquares=Cmd.ExecuteScalar()
if mCharedRegionSquares is Nothing then
mChargedRegionSquares="<UnKnown>"
end If
Catch Ex as OleDbException
Console.Writeln("Error occurred:{0}",Ex.Message)
Finally
if Not Conn is Nothing Then
Conn.Close()
End If
End Try
End If
Return mChargedRegionSquares
End Get
End Property
End Class
我们现在来分析上面的代码:(对于上面的代码中用到了数据库的连接,需要我们显示地Imports System.Data.OleDb,这是必须的。)
1. City类有表示城市的名称,城市的修建时间,城市的管辖地域这三个私有字段。
2. 构造函数初始化城市的名称和城市的修建时间这两个私有字段,并且将城市的管辖地域初始化为<Not Read>,用来指示将在第一次读取数据库时被读取到。
3. CityName属性获取和设置mCityName字段,而没有更深的处理,这个属性很薄地封装了mCityName的属性。
4. CityHistory属性根据城市的修建时间和当前的系统时间来计算这个城市的存在时间。这是一个很明显的派生特性的例子。注意:每次都重新计算CityHistory似乎比把这个时间存在字段里要合适的多。
5. ChargedRegionSquares属性可以进行懒惰初始化。在上面的例子中,城市管辖的地域存在数据库中,所以我们在构造函数中就不要对它求值,因为这样就可以当客户代码需要用到这个属性时,就与数据库连接来获得。这样明显提高了效率。
6. 我们在上面的代码中加入了错误和异常的处理,这对于一个健壮的面向对象的程序来说是必要的,相信大家和我有同感吧。