VB.Net中文教程(6) 母子物件關係 (轉)

worldblog發表於2007-12-06
VB.Net中文教程(6) 母子物件關係 (轉)[@more@]

請注意 ......
著作權所有人:物澤事業股份有限公司、
  MISOO技術顧問團隊、物件導向雜誌作者、等。
u本摘自 物件導向雜誌、精通物件觀念與技術等書籍著作。
u本檔案僅供您的參閱,請遵守著作權法,不得做其它商業用途。


主題:  母子物件關係
 副題:  Whole-Part關係


??????  內容  ??????
v 1. 特殊Whole-Part關係
  ---- 母子物件關係
v 2. 母子物件誰先誕生﹖
v 3. 類別繼承與母子物件

 

1. 特殊Whole-Part關係
  ---- 母子物件關係

  大家已經熟悉父子類別關係﹐也就是「繼承」關係了。於此說明另一種常見關係── 母子物件。一般稱之為「組合/部分」關係。日常生活中﹐處處可見到這種母子物件。例如﹐客廳內有沙發、桌子、椅子等等。客廳是母物件﹐沙發、桌子、椅子是子物件。再如計算機螢幕上的視窗內有按鈕、選擇表、對話盒等等。視窗是母物件﹐按鈕、選擇表是子物件。於此將說明如何建立母子物件關係。有了關係﹐母子就能互相溝通了。母子物件之間﹐如何溝通呢﹖也就是說﹐母物件如何呼叫子物件的呢﹖反過來﹐子物件如何呼叫母物件的程式呢﹖欲呼叫對方的程式﹐必先與對方建立關係才行。因之﹐如何建立母子物件關係﹐是頂重要之課題﹗
  請先看個例子﹐有兩個類別──Room和Desk。若Room代表房間﹐Desk代表房間內的桌子﹐則它們會誕生母子物件﹕

 

通常﹐您會依房間的大小來決定桌子的大小。因之﹐Desk物件應跟Room物件溝通﹐取得房間的大小﹐才決定自己的大小。若有個Room之參考﹐則Desk物件就能跟Room物件溝通了。於是﹐可設計下述VB程式:

'ex01.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.s
'----------------------------------------------------
Class Room
  Protected rSize As Double
  Shared mother As Room

  Public Sub New()
  motherObject = Me
  End Sub
  Shared Function GetMother() As Room
  GetMother = motherObject
  End Function
  Public Function GetSize() As Double
  GetSize = rSize
  End Function
End Class

Class Desk
  Protected dSize As Double
  Public Sub New()
  dSize = Room.GetMother().GetSize() * 0.18
  End Sub
  Public Function GetSize() As Double
  GetSize = dSize
  End Function
End Class
'----------------------------------------------------
Class MyRoom
  Inherits Room
  Private rd As Desk
 
  Public Sub New()
  rSize = 100
  rd = New Desk()
  End Sub
  Public Sub Show()
  MessageBox.Show("Room Size: " + str(rSize))
  MessageBox.Show("Desk Size: " + str(rd.GetSize()))
  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 FoDesigner.
  InitializeComponent()
  'TODO: Add any initialization after the InitializeComponent() call
  End Sub
  'Form overrs dispose to clean up the component list.
  Public Overrides Sub Dispose()
  MyBase.Dispose()
  components.Dispose()
  End Sub
#Region " Form Designer generated code "
  .....
#End Region
  Protected Sub Form1_Click( ByVal sender As Object,
  ByVal e As System.EventArgs)
  Dim r As New MyRoom()
  r.Show()
  End Sub
End Class

此程式輸出:
  Room Size: 100
  Desk Size:  18

Desk類別內之指令──
  Room.GetMother().GetSize()

就是Desk物件與Room物件之溝通了。MyRoom類別有個rd參考﹐指向子物件﹐如下﹕

 

母物件經由rd參考來跟子物件溝通。反過來﹐子物件經由Room.GetMother()參考跟母物件溝通。
2. 母子物件誰先誕生﹖
 
  在VB裡﹐有時候子物件比母物件早誕生完成。就像建造房子﹐建到半途﹐改而建造桌子﹐桌子建好了﹐再繼續把房子建完成。由於這不太合乎人們的日常生活習慣﹐令人感到困惑﹗在計算機上﹐也有類似的衝突。例如﹐在Windows 下﹐母視窗建好了﹐才建立窗內的按鈕、選擇表等子物件。但VB有時並非如此﹗因之﹐如何化解這衝突﹐是個重要的話題。茲拿上小節的例子來說明吧﹗該例子中﹐其誕生物件之過程為﹕
Step 1: 先誕生母物件。
 
 Step 2: 接著建立子物件。
 

這過程合乎人們的習慣﹐是美好的。其原因是﹕MyRoom類別在其建構者New()程式裡呼叫Desk的New()誕生Desk子物件。假若您寫下述程式﹐就出問題了﹗

'ex02.bas
'Some error here!
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'----------------------------------------------------
Class Room
  Protected rSize As Double
  Shared motherObject As Room

  Public Sub New()
  motherObject = Me
  End Sub
  Shared Function GetMother() As Room
  GetMother = motherObject
  End Function
  Public Function GetSize() As Double
  GetSize = rSize
  End Function
End Class

Class Desk
  Protected dSize As Double
  Public Sub New()
  dSize = Room.GetMother().GetSize() * 0.18
  End Sub
  Public Function GetSize() As Double
  GetSize = dSize
  End Function
End Class
'----------------------------------------------------
Class MyRoom
  Inherits Room
  Private rd As New Desk()
 
  Public Sub New()
  rSize = 100
  End Sub
  Public Sub Show()
  MessageBox.Show("Room Size: " + str(rSize))
  MessageBox.Show("Desk Size: " + str(rd.GetSize()))
  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 r As New MyRoom()
  r.Show()
  End Sub
End Class

其物件之建造過程為﹕
Step 1: 指令──
  Dim r As New MyRoom()
呼叫MyRoom()建構者New()。它再呼叫Room()建構者New()將Room部分建好。

Step 2: 指令──
  Private rd As New Desk()
  呼叫Desk建構者New()﹐建好Desk子物件。此時會執行指令── 
  dSize = Room.GetMother().GetSize() * 0.18

Step 3: MyRoom建構者New() 繼續將母物件建完成。
  此時會執行指令──  rSize=100
 
其中,GetSize()程式會取出rSize 值﹐但那時還未執行rSize=100 指令﹐那來
的rSize 值呢﹖所以出問題了。此程式可能輸出如下錯誤結果﹕
  Room Size: 100
  Desk Size: 0
 
如何解決上述問題呢﹖常見方法是﹕

  把會出問題的指令﹐從建構者程式中提出來﹐放到另一程式裡。

例如下述程式:

'ex03.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'----------------------------------------------------
Class Room
  Protected rSize As Double
  Shared motherObject As Room
 
  Shared Function GetMother() As Room
  GetMother = motherObject
  End Function
  Public Overridable Sub Create()
  motherObject = Me
  End Sub
  Public Function GetSize() As Double
  GetSize = rSize
  End Function
End Class

Class Desk
  Protected dSize As Double
  Public Sub Create()
  dSize = Room.GetMother().GetSize() * 0.18
  End Sub
  Public Function GetSize() As Double
  GetSize = dSize
  End Function
End Class
'----------------------------------------------------
Class MyRoom
  Inherits Room
  Private rd As New Desk()
 
  Public Sub New()
  Me.Create()
  End Sub
  Public Overrides Sub Create()
  MyBase.Create()
  rSize = 100
  rd.Create()
  End Sub
  Public Sub Show()
  MessageBox.Show("Room Size: " + str(rSize))
  MessageBox.Show("Desk Size: " + str(rd.GetSize()))
  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 r As New MyRoom()
  r.Show()
  End Sub
End Class

此程式輸出:
  Room Size: 100
  Desk Size:  18

像指令── dSize = Room.GetMother().GetSize() * 0.18﹐已挪到新設的Create()程式裡。待母物件完全建好了﹐才會呼叫這Create()程式﹐GetSize() 就能取得正確值了。MyRoom的New()呼叫Create()程式時﹐母子物件皆已建造完成了。Create()內部依人們的習慣來設定物件之值,例如建立母子物件之關係。

 

如此就不會出問題了。
  New()與Create()分離之後,MyRoom類別裡的指令:

Class MyRoom
  Inherits Room
  Private rd As New Desk()
 
  Public Sub New()
  Me.Create()
  End Sub
........

也能寫為:
Class MyRoom
  Inherits Room
  Private rd As Desk
 
  Public Sub New()
  rd = New Desk()
  Me.Create()
  End Sub
........

只要確保Desk類別的指令──
  dSize = Room.GetMother().GetSize() * 0.18

是在MyRoom類別的指令──
  rSize = 100

之後執行即可了。
  上述的子物件是透過Shared 程式來取得母物件的參考值﹐然後才跟母物件溝通。如果不透過Shared程式,也可以採取下述方法:

'ex04.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'----------------------------------------------------
Class Room
  Protected rSize As Double
 
  Public Overridable Sub Create()
  End Sub
  Public Function GetSize() As Double
  GetSize = rSize
  End Function
End Class

Class Desk
  Protected dSize As Double
  Protected myMother As Room
 
  Public Sub Create(ByVal mo As Room)
  myMother = mo
  dSize = myMother.GetSize() * 0.18
  End Sub
  Public Function GetSize() As Double
  GetSize = dSize
  End Function
End Class
'----------------------------------------------------
Class MyRoom
  Inherits Room
  Private rd As Desk
 
  Public Sub New()
  rd = New Desk()
  Me.Create()
  End Sub
  Public Overrides Sub Create()
  rSize = 100
  rd.Create(Me)
  End Sub
  Public Sub Show()
  MessageBox.Show("Room Size: " + str(rSize))
  MessageBox.Show("Desk Size: " + str(rd.GetSize()))
  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 r As New MyRoom()
  r.Show()
  End Sub
End Class

此程式輸出:
  Room Size: 100
  Desk Size:  18

Desk之物件含個參考 myMother ﹐指向其母物件。這項關係是在母子物件皆建好時﹐才由Create()程式所建立的。於是﹐建立出母子物件之關係﹕

 

綜上所述,當MyRoom類別使用如下指令──
 Private rd As New Desk()

時,才必須把New()與Create()分離。如果使用如下指令──

Private rd As Desk
Public Sub New()
  rd = New Desk()
  .....
End Sub
 
就不必分離了,原因是:New()與Create()的執行順序是一致的,例如兩者可合併如下的VB程式:

'ex05.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'----------------------------------------------------
Class Room
  Protected rSize As Double
 
  Public Function GetSize() As Double
  GetSize = rSize
  End Function
End Class

Class Desk
  Protected dSize As Double
  Protected myMother As Room
 
  Public Sub New(ByVal mo As Room)
  myMother = mo
  dSize = myMother.GetSize() * 0.18
  End Sub
  Public Function GetSize() As Double
  GetSize = dSize
  End Function
End Class
'----------------------------------------------------
Class MyRoom
  Inherits Room
  Private rd As Desk
 
  Public Sub New()
  rSize = 100
  rd = New Desk(Me)
  End Sub
  Public Sub Show()
  MessageBox.Show("Room Size: " + str(rSize))
  MessageBox.Show("Desk Size: " + str(rd.GetSize()))
  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 r As New MyRoom()
  r.Show()
  End Sub
End Class

此程式輸出:
  Room Size: 100
  Desk Size:  18

 

 


3. 特類別繼承與母子物件

  您已很熟悉父子類別關係了﹐這繼承關係的另一面是母子物件關係。例如﹐

'ex06.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'----------------------------------------------------
Class Room
  Private mother As MyRoom
 
  Public Sub New(ByVal mo As MyRoom)
  mother = mo
  End Sub
  Public Function GetID() As String
  GetID = mother.yourID()
  End Function
End Class

Class MyRoom
  Inherits Room
 
  Public Sub New()
  MyBase.New(Me)
  End Sub
  Public Function yourID() As String
  yourID = "VIP888"
  End Function
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 r As New MyRoom()
  MessageBox.Show(r.GetID())
  End Sub
End Class

此程式輸出﹕
  VIP888

MyRoom()建構者New()將Me值給 Room的mother參考。於是mother參考到母物件r。Room類別是MyRoom之父類別﹐但Room之物件卻是MyRoom之子物件。
 

 

 如果將上述Room的指令:

Public Function GetID() As String
  GetID = mother.yourID()
End Function

更改為:

Public Function GetID() As String
  GetID = Me.yourID()  'Error!!
End Function

就錯了。因為Room類別裡沒有定義yourID()程式。事實上,VB的繼承機制裡已經有母物件的參考值了,VB的Overridable機制就是基於它而呼叫到子類別(母物件)的程式的。例如上述程式相當於:

'ex07.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'----------------------------------------------------
Class Room
  Public Function GetID() As String
  GetID = Me.yourID()
  End Function
  Public Overridable Function yourID() As String
  End Function
End Class

Class MyRoom
  Inherits Room
 
  Public Sub New()
  MyBase.New()
  End Sub
  Public Overrides Function yourID() As String
  yourID = "VIP888"
  End Function
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 r As New MyRoom()
  MessageBox.Show(r.GetID())
  End Sub
End Class

  此程式輸出﹕
  VIP888

上述ex07.bas比ex06.bas好的地方是:
  ex06.bas程式的Room類別裡面用到MyRoom的名稱。而ex07.bas程式的Room類別裡面並沒用到MyRoom的名稱,因此Room類別可以先設計,MyRoom類別能後來才設計,這是繼承機制的目的之一。不過,如果您一定不想用繼承與Overridable概念的話,可使用VB的Interface機制來改善ex06.bas程式,如下:

'ex08.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'----------------------------------------------------
Interface IMyRoom
  Function yourID() As String
End Interface

Class Room
  Private mother As IMyRoom
 
  Public Sub New(ByVal mo As IMyRoom)
  mother = mo
  End Sub
  Public Function GetID() As String
  GetID = "RoomID: " + mother.yourID()
  End Function
End Class

Class MyRoom
  Implements IMyRoom
  Private base As Room
 
  Public Sub New()
  base = New Room(Me)
  End Sub
  Public Function yourID() As String Implements IMyRoom.yourID
  yourID = "VIP888"
  End Function
  Public Function GetID() As String
  GetID = base.GetID()
  End Function
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 r As New MyRoom()
  MessageBox.Show(r.GetID())
  End Sub
End Class

此程式輸出﹕
  RoomID: VIP888

  一般使用委託(Delegation)來代替繼承時,常用的手法。然而上述ex08.bas程式的MyRoom類別裡面用到了Room名稱,如果您不希望如此,可定義一個IRoom介面,供MyRoom類別使用,如下程式:

'ex09.bas
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
'----------------------------------------------------
Interface IMyRoom
  Function yourID() As String
End Interface

Interface IRoom
  Function GetID() As String
  Sub Connect(ByVal m As IMyRoom)
End Interface

Class Room
  Implements IRoom
  Private motherObject As IMyRoom
 
  Public Function GetID() As String Implements IRoom.GetID
  GetID = motherObject.yourID() + "  ***"
  End Function
  Public Sub Connect(ByVal m As IMyRoom) Implements IRoom.Connect
  motherObject = m
  End Sub
End Class

Class MyRoom
  Implements IMyRoom
  Private base As IRoom
 
  Public Sub Connect(ByVal r As IRoom)
  base = r
  r.Connect(Me)
  End Sub
  Public Function yourID() As String Implements IMyRoom.yourID
  yourID = "Dog888"
  End Function
  Public Function GetID() As String
  GetID = base.GetID()
  End Function
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 my As New MyRoom()
  Dim base As New Room()
  my.Connect(base)
  MessageBox.Show(my.GetID())
  End Sub
End Class

此程式輸出﹕
  RoomID: VIP888

  Room類別裡面沒用到MyRoom名稱,而且MyRoom類別裡沒有用到Room名,因此兩個類別可獨立設計。這是分散式軟體,如MTS( transaction server)等裡的常用手法。n

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-988978/,如需轉載,請註明出處,否則將追究法律責任。

相關文章