请注意 ......
著作权所有人:物泽计算机事业股份有限公司、
MISOO对象技术顾问团队、对象导向杂志作者、等。
u本文件摘自 对象导向杂志、精通对象观念与技术等书籍著作。
u本文件仅供您的参阅,请遵守著作权法,不得做其它商业用途。
主题: 共享成员(Shared Member)
?????? 内容 ??????
v 1. 共享资料成员
v 2. 共享程序成员
您已经习惯像 New Employee("Tom", 25)这样的指令了,看到这个指令可以想向它是:Employee.New("Tom", 25),于是不难想象到,原来类别也是对象!这个类别对象(Class Object)接到New()讯息时,就去诞生一个对象,原来类别对象就是妈妈对象(Meta Object)!妈妈是小孩共有的,妈妈的资料值是小孩共享的,妈妈的程序是小孩共享的。本文就介绍这种共享的资料成员和程序成员。
1. 共享资料成员
对象拥有自己的空间﹐也拥有自己的资料﹔对象之间的沟通(交换资料)方法是个重要问题。如果只想传递某项资料时﹐该如何呢﹖
图1、 对象间之沟通
有数种可行方法﹐请看个例子﹕
'ex01.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'----------------------------------------------------
Class Employee
Private emp_name As String
Public salary As Double
Public Overloads Sub New(ByVal na As String)
emp_name = na
End Sub
Public Overloads Sub New(ByVal na As String, ByVal s As Double)
emp_name = na
salary = s
End Sub
Public Sub Display()
MessageBox.Show("Name: " + emp_name + " Salary: " + str(salary))
End Sub
End Class
'-----------------------------------------------------
Public Class Form1
Inherits System.WinForms.Form
Public Sub New()
MyBase.New()
Form1 = Me
'This call is required by the Win Form Designer.
InitializeComponent()
'TODO: Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Public Overrides Sub Dispose()
MyBase.Dispose()
components.Dispose()
End Sub
#Region " Windows Form Designer generated code "
......
#End Region
Protected Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim e1 As New Employee("Tom")
Dim e2 As New Employee("Lily", 20000)
e1.salary = e2.salary + 5000
e1.Display()
e2.Display()
End Sub
End Class
此程序输出如下﹕
Name:Tom Salary:25000
Name:Lily Salary:20000
这宣告salary为public变量﹐让main()能直接使用salary变量。指令:
e1.salary = e2.salary + 5000
把对象e2之salary值加上5000﹐然后存入e1之salary变量中。此程序﹐重复定义了建构者程序── New()﹐宣告对象e1及e2时﹐就有两种选择﹕只输入姓名﹐或同时输入姓名及薪资额。请注意一项缺点﹕把salary宣告为Public变量﹐让Form1_Click()可使用salary变量名称﹐直接把资料存入对象中。若其它程序也依样画葫芦﹐任意把值存入salary中﹐那么salary值可能无意中遭破坏了。
如果salary资料并非机密﹐尚无所谓。如果salary含有极重要资料﹐就得谨慎小心了。就像一颗炸弹(Employee类别)内的炸药(salary资料)可能无意中因外面因素(温度升高等)而引起变化导致爆炸﹐就危险了。
图2、类别之保护功能
因之﹐为让对象之间互相传递资料而把资料成员宣告为Public变量并不甚妥当。希望能像炸弹一样﹐只能经由信管(程序成员)使内部的炸药(salary)起化学作用。于是﹐宣告salary为Private变量﹐把它封藏在Employee的保护之中才安全。类别(及对象)之用途和手表、灯泡、蜗牛的外壳是一样的──保护内部之资料﹐只限特定之管道才能存取资料。然而一旦把salary藏入Employee壳中﹐上述程序就出问题了﹐如下﹕
'ex02.bas
'Some Error Here!
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'----------------------------------------------------
Class Employee
Private emp_name As String
Private salary As Double
Public Overloads Sub New(ByVal na As String)
emp_name = na
End Sub
Public Overloads Sub New(ByVal na As String, ByVal s As Double)
emp_name = na
salary = s
End Sub
Public Sub Display()
MessageBox.Show("Name: " + emp_name + " Salary: " + str(salary))
End Sub
End Class
'-----------------------------------------------------
Public Class Form1
Inherits System.WinForms.Form
Public Sub New()
MyBase.New()
Form1 = Me
'This call is required by the Win Form Designer.
InitializeComponent()
'TODO: Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Public Overrides Sub Dispose()
MyBase.Dispose()
components.Dispose()
End Sub
#Region " Windows Form Designer generated code "
.....
#End Region
Protected Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim e1 As New Employee("Tom")
Dim e2 As New Employee("Lily", 20000)
e1.salary = e2.salary + 5000 'Error !
e1.Display()
e2.Display()
End Sub
End Class
因为salary变成 Private变量﹐Form1_Click()不能使用它﹗
为了将传递之过程纳入类别保护之中﹐有数种方法,例如:
'ex03.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'----------------------------------------------------------------------
Class Employee
Private Shared temp As Double
Private emp_name As String
Private salary As Double
Shared Sub New()
temp = 0
End Sub
Public Overloads Sub New(ByVal na As String)
emp_name = na
End Sub
Public Overloads Sub New(ByVal na As String, ByVal s As Double)
emp_name = na
salary = s
End Sub
Public Sub addSalaryTo(ByVal emp As Employee , ByVal money As Double)
salary = emp.salary + money
End Sub
Public Sub Display()
MessageBox.Show("Name: " + emp_name + " Salary: " + str(salary))
End Sub
End Class
'------------------------------------------------------------------------------
Public Class Form1
Inherits System.WinForms.Form
Public Sub New()
MyBase.New()
Form1 = Me
'This call is required by the Win Form Designer.
InitializeComponent()
'TODO: Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Public Overrides Sub Dispose()
MyBase.Dispose()
components.Dispose()
End Sub
#Region " Windows Form Designer generated code "
......
#End Region
Protected Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim e1 As New Employee("Tom")
Dim e2 As New Employee("Lily", 20000)
e1.addSalaryTo(e2, 5000)
e1.Display()
e2.Display()
End Sub
End Class
此程序输出如下﹕
Name:Tom Salary:25000
Name:Lily Salary:20000
指令── salary = emp.salary + money 已经在Employee类别的保护中,这是不错的做法。
另一种方法是采用共享资料成员,是同一类别里各对象皆共享其值;一般资料成员的值是封装于各对象内,别的对象是拿不到的。共享资料成员的值是在对象之外,但封装在类别内,只要是该类别的对象皆能拿到该值。因为一般资料成员的值封装于对象内,所以又称为对象变量(Instance Variable);共享资料成员封装于类别,所以又称为类别变量(Class Variable)。上述程序可改用共享变量如下﹕
'ex04.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'----------------------------------------------------
Class Employee
Private emp_name As String
Private salary As Double
Private Shared temp As Double
Public Overloads Sub New(ByVal na As String)
emp_name = na
End Sub
Shared Sub New()
temp = 0
End Sub
Public Overloads Sub New(ByVal na As String, ByVal s As Double)
emp_name = na
salary = s
End Sub
Public Sub output()
temp = salary
End Sub
Public Sub input()
salary = temp
End Sub
Public Sub add_temp(ByVal money As Double)
temp = temp + money
End Sub
Public Sub Display()
MessageBox.Show("Name: " + emp_name + " Salary: " + str(salary))
End Sub
End Class
'-----------------------------------------------------
Public Class Form1
Inherits System.WinForms.Form
Public Sub New()
MyBase.New()
Form1 = Me
'This call is required by the Win Form Designer.
InitializeComponent()
'TODO: Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Public Overrides Sub Dispose()
MyBase.Dispose()
components.Dispose()
End Sub
#Region " Windows Form Designer generated code "
......
#End Region
Protected Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim e1 As New Employee("Tom")
Dim e2 As New Employee("Lily", 20000)
e2.output()
e2.add_temp(5000)
e1.input()
e1.Display()
e2.Display()
End Sub
End Class
此程序输出如下﹕
Name:Tom Salary:25000
Name:Lily Salary:20000
宣告指令── Private Shared temp As Double
说明了﹐temp是共享资料成员。兹归纳,共享资料成员与一般资料成员之区别为:
「共享资料成员是各对象公用的资料﹐
而一般资料成员是对象之私有资料。」
当第1次诞生对象时﹐共享资料成员就由Shared Sub New()诞生了。当执行到指令──
Dim e1 As New Employee("Tom")
就诞生对象e1。但temp比e1早诞生。接下来﹐执行指令──
Employee e2( "Lily", 20000 )
它诞生对象e2。此时Employee类别之内容为﹕
图3、类别沟通方式──使用共享资料成员
其中﹐e1及e2各含有一份emp_name及salary资料。然而整个Employee类别才只有一份temp资料﹐对象共同分享此temp内之资料。Employee类别里的任何对象皆可视temp为其资料成员来使用﹐但事实上只有一个temp。由于它属于所有的对象﹐所以各对象皆可经由程序成员存取temp资料。重要的是﹐temp已在Employee类别的保护之中﹐藉由它传递资料就无受外界干扰之虞了。
共享资料成员除了供对象之间的沟通之用外﹐还有其重要的用途──记载类别的状况﹐例如记录该类别共诞生了多少对象。请看个例子﹕
'ex05.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'-----------------------------------------------------------
Class Employee
Private emp_name As String
Private salary As Double
Private Shared counter As Integer
Private Shared sum As Double
Shared Sub New()
counter = 0
sum = 0
End Sub
Public Sub New(ByVal na As String, ByVal s As Double)
emp_name = na
salary = s
counter = counter + 1
sum = sum + salary
End Sub
Public Sub Display_Avg()
MessageBox.Show("The number of employee : " + str(counter))
MessageBox.Show("Average salary : " + str(sum / counter))
End Sub
Public Sub Display()
MessageBox.Show("Name: " + emp_name + " Salary: " + str(salary))
End Sub
End Class
'-------------------------------------------------------------------
Public Class Form1
Inherits System.WinForms.Form
Public Sub New()
MyBase.New()
Form1 = Me
'This call is required by the Win Form Designer.
InitializeComponent()
'TODO: Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Public Overrides Sub Dispose()
MyBase.Dispose()
components.Dispose()
End Sub
#Region " Windows Form Designer generated code "
......
#End Region
Protected Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim e1 As New Employee("Tom", 25000)
Dim e2 As New Employee("Lily", 20000)
e1.Display()
e2.Display()
e1.Display_Avg()
End Sub
End Class
此程序输出﹕
Name: Tom Salary: 25000
Name: Lily Salary: 20000
The number of employee : 2
Average salary : 22500
counter 记录类别中含有多少对象。sum 储存各对象的salary值之总和。于此﹐必须给予 counter及 sum初值。共享变量之初值设定格式为──
Shared Sub New()
共享资料成员1 = 初值
共享资料成员2 = 初值
......
End Sub
计算机开始执行,诞生第1个对象时﹐各共享成员就诞生了﹐而且设定了初值。此时Employee类别的内容为﹕
对象诞生时﹐会去执行建构者程序──Public Sub New(...),先把各资料存入对象之私有成员中﹔同时也执行指令──counter = counter + 1﹐使共享变量 counter值加上 1。此外﹐也执行指令──sum = sum + salary ﹐把e1之salary值加到 sum里头。此时共享变量的内容为﹕
counter 值为 1﹐表示Employee内已诞生一个对象。接着﹐诞生个体e2﹐计算机又执行建构者程序──Public Sub New(...)。它把资料存入对象e2中﹐使 counter加上 1﹐也把e2之salary值加到 sum中。此时﹐共享变量之内容为﹕
注意﹐e1含有 4个资料成员──emp_name、salary、counter 及sum ﹔其值分别为 "Tom"、 25000、 2及 45000。同时e2含有4 个资料成员──emp_name、salary、counter 及 sum﹔其值分别为"Lily"、20000 、2 及 45000。只是e1之 counter值等于e2之 counter值﹐同时e1之sum 值等于e2之 sum值。
2. 共享程序成员
刚才介绍过共享资料成员(Shared Data Member)之观念。共享资料成员是各对象的公用数据成员﹐但并不属于任何对象﹐而是属于类别的。除了资料成员之外,也有共享程序成员,它是属于类别的﹐而并不属于任何对象﹐其不是用来存取某对象内的值﹐而只储存取共享资料成员之值。任何一般程序成员皆可呼叫共享程序成员来存取共享成员之值。例如﹕
'ex06.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'----------------------------------------------------------------------
Class Employee
Private emp_name As String
Private salary As Double
Private Shared counter As Integer
Private Shared sum As Double
Shared Sub New()
counter = 0
sum = 0
End Sub
Public Sub New(ByVal na As String, ByVal s As Double)
emp_name = na
salary = s
counter = counter + 1
sum = sum + salary
End Sub
Shared Function NumberOfObject() As Integer
NumberOfObject = counter
End Function
Shared Function Average() As Double
Average = sum / counter
End Function
Public Sub Display()
MessageBox.Show("Name: " + emp_name + " Salary: " + str(salary))
End Sub
End Class
'-------------------------------------------------------------------------
Public Class Form1
Inherits System.WinForms.Form
Public Sub New()
MyBase.New()
Form1 = Me
'This call is required by the Win Form Designer.
InitializeComponent()
'TODO: Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Public Overrides Sub Dispose()
MyBase.Dispose()
components.Dispose()
End Sub
#Region " Windows Form Designer generated code "
.......
#End Region
Protected Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim e1 As New Employee("Tom", 25000)
Dim e2 As New Employee("Lily", 20000)
MessageBox.Show(str(e1.NumberOfObject()) + " objects")
MessageBox.Show("Avgerage: " + str(e2.Average()))
Dim e3 As New Employee("Linda", 15000)
MessageBox.Show(str(e3.NumberOfObject()) + " objects")
MessageBox.Show("Average: " + str(e3.Average()))
End Sub
End Class
此程序输出:
2 objects
Average: 22500
3 objects
Average: 20000
还记得吗﹖一般程序成员是用来处理对象内的资料﹐所以呼叫一般程序成员之格式为──
对象. 一般程序成员
呼叫这程序成员的目的是来处理此对象之内容。共享程序成员之目的并非在于处理对象之内容﹐而是存取共享资料成员之值或处理些有关于整个类别的事情。因之﹐呼叫共享程序成员的格式为──
类别 . 共享程序成员
呼叫这共享程序成员之目的是来处理此类别之资料或有关之事情。
一种格式──
对象 . 共享程序成员
每个对象皆来自某个类别﹐VB看到对象名称时﹐可对应到它的类别﹐所
以VB在编译这种格式时﹐会将之转换成为﹕
类别 . 共享程序成员
亦即﹐指令── c1.NameOfObject()
及 c2.NameOfObject()
皆会转换成为-- Employee.NameOfObject()
同理﹐e1.Average()也就相当于Employee.Average() 了。
由上述可知共享程序成员并非处理某个特定的对象值﹐所以没有必要去呼叫一般的程序成员。VB也就禁止共享程序成员呼叫一般的程序成员。不过﹐反之一般程序成员却可以呼叫共享程序成员﹐以便必要时可藉由共享程序成员来取得有关于整个类别的资料。例如:
'ex07.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'------------------------------------------------------------------------------
Class Employee
Private emp_name As String
Private salary As Double
Private Shared counter As Integer
Private Shared sum As Double
Shared Sub New()
counter = 0
sum = 0
End Sub
Public Sub New(ByVal na As String, ByVal s As Double)
emp_name = na
salary = s
counter = counter + 1
Me.sum = Employee.sum + salary
End Sub
Public Function GetSalary() As Double
GetSalary = salary
End Function
Shared Function NumberOfObject() As Integer
NumberOfObject = counter
End Function
Shared Function Average() As Double
Dim k As Double
Average = Employee.sum / Employee.counter
' salary = salary + 2000 ' Error!
' k = GetSalary() ' Error!
End Function
Public Sub Disp()
MessageBox.Show(str(Employee.NumberOfObject()) + " objects")
MessageBox.Show("Avgerage: " + str(Employee.Average()))
End Sub
End Class
'-----------------------------------------------------
Public Class Form1
Inherits System.WinForms.Form
Public Sub New()
MyBase.New()
Form1 = Me
'This call is required by the Win Form Designer.
InitializeComponent()
'TODO: Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Public Overrides Sub Dispose()
MyBase.Dispose()
components.Dispose()
End Sub
#Region " Windows Form Designer generated code "
.......
#End Region
Protected Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim e1 As New Employee("Tom", 25000)
Dim e2 As New Employee("Lily", 20000)
e2.disp()
Dim e3 As New Employee("Linda", 15000)
e3.disp()
End Sub
End Class
此程序输出:
2 objects
Average: 22500
3 objects
Average: 20000
共享程序,上述Disp()是一般程序成员,此程序内的指令:
Public Sub Disp()
MessageBox.Show(str(Employee.NumberOfObject()) + " objects")
......
End Sub
这就呼叫了共享的NumberOfObject()程序。
共享程序不可呼叫一般程序,上述Average()是共享程序,它不能呼叫一般程序,所以Average()内的指令有错:
Shared Function Average() As Double
Dim k As Double
Average = Employee.sum / Employee.counter
' salary = salary + 2000 ' Error!
' k = GetSalary() ' Error!
End Function
请记得,共享程序是类别对象(妈妈)的程序,妈妈与小孩各有隐私(封装),小孩呼叫妈妈程序(即共享程序)的格式为:
妈妈 . 共享程序()
例如:Employee.Average()
或 对象 . 共享程序()
例如: e1.Average() 或 Me.Average()。
但妈妈呼叫小孩程序的格式为:
对象 . 一般程序()
例如: e1.Average()。
因为妈妈程序里没有Me参考变量,所以上述的 k = GetSalary()指令相当于k = Me.GetSalary(),就错掉了。请再看一个例子:
'ex08.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'--------------------------------------------------------------------------------
Class Employee
Private emp_name As String
Private salary As Double
Private Shared counter As Integer
Private Shared p(10) As Employee
Shared Sub New()
counter = 0
End Sub
Public Sub New(ByVal na As String, ByVal s As Double)
emp_name = na
salary = s
p(counter) = Me
counter = counter + 1
End Sub
Shared Function GetObject(ByVal index As Integer) As Employee
Dim sp As Employee
If index > counter Or index < 0 Then
sp = Nothing
Else
sp = p(index)
End If
GetObject = sp
End Function
Public Sub Display()
MessageBox.Show("Name: " + emp_name + " Salary: " + str(salary))
End Sub
Shared Sub Disp(ByVal e As Employee)
e.Display()
End Sub
End Class
'-----------------------------------------------------------------------------------------------------
Public Class Form1
Inherits System.WinForms.Form
Public Sub New()
MyBase.New()
Form1 = Me
'This call is required by the Win Form Designer.
InitializeComponent()
'TODO: Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Public Overrides Sub Dispose()
MyBase.Dispose()
components.Dispose()
End Sub
#Region " Windows Form Designer generated code "
.......
#End Region
Protected Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim e1 As New Employee("Tom", 25000)
Dim e2 As New Employee("Lily", 20000)
Dim e3 As New Employee("Linda", 15000)
Dim e4 As New Employee("Alvin", 5000)
Dim emp As Employee
emp = Employee.GetObject(2)
Employee.Disp( emp )
emp = Employee.GetObject(1)
Employee.Disp( emp )
End Sub
End Class
此程序输出:
Name: Linda Salary: 15000
Name: Lily Salary: 20000
在妈妈对象里有一个p(10)数组,每次诞生对象时,就将新对象的参考值存入数组里。妈妈程序GetObject(i)则从数组中取出第i个对象,并传回来。妈妈程序使用格式:
对象 . 一般程序()
呼叫小孩的Display()程序。
上述程序相当于:
'ex09.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'--------------------------------------------------------------------------------
Class Employee
Private emp_name As String
Private salary As Double
Private Shared counter As Integer
Private Shared p(10) As Employee
Private Shared current As Employee
Shared Sub New()
counter = 0
End Sub
Public Sub New(ByVal na As String, ByVal s As Double)
emp_name = na
salary = s
p(counter) = Me
counter = counter + 1
End Sub
Shared Sub FindObject(ByVal index As Integer)
Dim sp As Employee
If index > counter Or index < 0 Then
sp = Nothing
Else
sp = p(index)
End If
current = sp
End Sub
Shared Sub Disp()
MessageBox.Show( "Name: " + current.emp_name +
" Salary: " + str(current.salary))
End Sub
End Class
'-----------------------------------------------------------------------------------------------------
Public Class Form1
Inherits System.WinForms.Form
Public Sub New()
MyBase.New()
Form1 = Me
'This call is required by the Win Form Designer.
InitializeComponent()
'TODO: Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Public Overrides Sub Dispose()
MyBase.Dispose()
components.Dispose()
End Sub
#Region " Windows Form Designer generated code "
.......
#End Region
Protected Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim e1 As New Employee("Tom", 25000)
Dim e2 As New Employee("Lily", 20000)
Dim e3 As New Employee("Linda", 15000)
Dim e4 As New Employee("Alvin", 5000)
Employee.FindObject(2)
Employee.Disp()
Employee.FindObject(1)
Employee.Disp()
End Sub
End Class
此程序输出:
Name: Linda Salary: 15000
Name: Lily Salary: 20000
妈妈程序FindObject(i)则从数组中找到第i个对象,并记录在current变量里,不传回来。n