第十九章物件導向程式設計簡介
19.1前言
物件導向程式設計,是程式設計界熱烈話題、主流,觀念比較難懂抽象,當程式編輯者如已熟悉基本的程式設計作業工作,想要進一步提昇程式設計能力的深度及廣度時,就必須熟諳物件導向程式設計的『觀念及方法』。物件導向本身使用很多抽象的「方法及理論」,而不再是只專注於程式設計的實質內容。VB.Net
是一種物件導向程式(Object-Oriented
Programming,OOP)設計語言。物件導向程式設計是程式設計的一種方式,在物件導向程式設計中,程式設計師可以用類別(Class)區塊,將相關的屬性(Property)、方法(Methods,函數、程式)、事件(Events)等程式碼包夾在程式命名空間(NameSpace,資料圖書館)區塊中,稱之為「類別」,所以程式設計師可以設計許多的類別,以便於日後重複使用,或將其封裝成為動態資料連接庫(Dll)供他人使用。OOP程式設計的有三大特性:(1)封裝(Encapsulation),(2)繼承(Inheritance),(3)多態或多型(Polymorphism)等,可以讓程式維護、修改、擴充更容易。本章與下一章均會討論與類別有關的內容,本章將著重於一般名詞及初級觀念的解釋,下一章則較偏重實際內容的討論。
19.2封裝
封裝(Encapsulation)有隱藏資料的意涵,將不需要公開的資料宣告為私有,不讓外部存取,以避免不必要的修改,也使程式的分工、維護、擴充更容易。封裝後之程式碼可以重複使用,也可以供多數人使用。如下面的程式碼都是計算矩形面積的程式,功用相同,但資料的宣告及存取方法有別。觀察下面的程式碼,程式(1)未使用類別,直接呼叫使用帶有引數的私有函數(Function,areaT()),私有函數的引數直接包夾在areaT()中,直接傳遞引數,(2)則利用公有類別、公有引數、函數area2(),利用建立物件方式直接傳遞公有引數,(3)利用公有類別、建構式(New)、函數area3(),將引數宣告為私有,透過帶有引數的建構式引數傳遞與呼叫程式溝通、對口,(4)則利用公有類別、公有get_set屬性程序、函數area4(),引數與(3)一樣仍然宣告為私有,透過get_set屬性程序,以間接方式傳遞。(3)及(4)將引數_width及_height宣告為Private,透過關鍵字New()或get_set屬性與外部溝通,因此(3)及(4)均有封裝的特性,引數傳遞為間接方式,程式運用有點像在黑箱中作業。
(1)直接呼叫私有函數
areaT(引數)
'Method1
Private
Sub btnClassArea1_Click(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles
btnClassArea1.Click
Dim myWidth = InputBox("請輸入長方形寬度")
'取得變數width值
Dim myHeight = InputBox("請輸入長方形高度")
Dim area1 = areaT(myWidth, myHeight)
MsgBox("Method1:area= " & area1)
End Sub
Private
Function areaT(ByVal wid
As Double,
ByVal ht As
Double) As
Double
If wid <= 0 Or
ht <= 0 Then
MsgBox("輸入資料有誤,請檢查後重來)")
Exit
Function
Else
Return wid * ht
End If
End Function
(2)利用公有類別、公有引數、函數area2(無引數)
'Method2
Private
Sub btnClassArea2_Click(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles
btnClassArea2.Click
Dim myWidth = InputBox("請輸入長方形寬度")
'取得變數width值
Dim myHeight = InputBox("請輸入長方形高度")
'取得ClsArea公有變數height值
Dim B As
New ClsArea2
B.width = myWidth
B.height = myHeight
Dim area2 As
Double = B.area2()
MsgBox("Method2:public width,height, area= "
& area2)
End Sub
Public
Class ClsArea2
Public width As
Double
Public height
As Double
Public Function
area2() As
Double
'傳回
_width*_height
Return width * height
End Function
End
Class
(3)利用公有類別、建構式(New(引數))、函數area3()
'Method3
Private
Sub btnClassArea3_Click(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles
btnClassArea3.Click
Dim myWidth = InputBox("請輸入長方形寬度")
'取得變數width值
Dim myHeight = InputBox("請輸入長方形高度")
'取得ClsArea公有變數height值
Dim A As
New ClsArea3(myWidth, myHeight)
Dim area3 As
Double = A.area3
MsgBox("Method3:private
_width,_height,New(width,ht),area=Area= " & area3)
End Sub
Public
Class ClsArea3
Private _width
As Double
Private _height
As Double
Private _errNo
As Integer
Public Sub
New(ByVal
width As Double,
ByVal ht As
Double)
If width <= 0
Or ht <= 0 Then
MsgBox("資料輸入有誤,請檢查後重來")
_errNo = -1
Exit
Sub
Else
_errNo = 0
_width = width
_height = ht
End If
End Sub
Public Function
area3() As
Double
If _errNo < 0
Then
MsgBox("資料輸入有誤,請檢查後重來")
Return -1
Else
Return _width * _height
End If
End Function
End
Class
(4)利用公有類別、公有get_set屬性程序、函數area4()
'Method4
Private
Sub butClassArea4_Click(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles
butClassArea4.Click
Dim myWidth = InputBox("請輸入長方形寬度")
'取得變數width值
Dim myHeight = InputBox("請輸入長方形高度")
'取得ClsArea公有變數height值
Dim A As
New ClsArea4
Dim myError As
Double
A.width = myWidth '透過ClsArea2屬性width取得tWidth值
A.height = myHeight '透過ClsArea2屬性height取得theight值
myError = A.width '如為負值代表輸入資料錯誤
myError = myError + A.height '如為負值代表輸入資料錯誤
If myError < 0
Then
MsgBox("Error= " &
myError & "
資料輸入有誤,請檢查後重來")
Exit
Sub
Else
Dim Area4 As
Double = A.area4()
'注意!!!原形函數area()並無引數
MsgBox("Method4:private
_width,_height,New(),area=Area= " & Area4)
End If
End Sub
End
Class
Public
Class ClsArea4
Private _width
As Double
Private _height
As Double
Public Property
width() As
Double '設定width屬性
Get
Return _width
End Get
Set(ByVal
value As Double)
If value < 0
Then
MsgBox("輸入的寬度資料為負值??!!,請重來")
_width = -1.0E+30 '輸入負值後以-1.0E+30代表
Exit
Property
Else
_width = value
End
If
End Set
End Property
Public Property
height() As
Double '設定height屬性
Get
Return _height
End Get
Set(ByVal
value As Double)
If value < 0
Then
MsgBox("輸入的長度資料為負值??!!,請重來")
_height = -1.0E+30 '輸入負值後以-1.0E+30代表
Exit
Property
Else
_height = value
End
If
End Set
End Property
Public Function
area4() As
Double
'傳回
_width*_height
Return _width * _height
End Function
End
Class
19.3繼承
類別Class可透過Inheritance(繼承)來繼承原有類別所有Public屬性、方法及事件,如有需要也可以修改或擴充Class的內容。可以被繼承的原始類別稱為「基礎Class」或「父Class」,繼承基礎Class的新Class稱為「衍生Class」或「子Class」。衍生Class會繼承基礎Class的「Public成員」,然後依需求直接使用或是改寫基礎Class的功能與擴充衍生其他功能。.NET
Framework繼承關係只會向下傳遞,而且.NET Framework不支援多重繼承,每一個Class只能繼承一個Class。VB.NET中的視窗表單(WinForm)、控制項等都是一種類別,因此它都可以拿來作為基礎父類別,下面的程式碼及執行成果就是利用繼承VB.NET的文字方塊類別的最簡單繼承例子。
Public
Class Form2
Class TextBox
'類別TextBox
Inherits System.Windows.Forms.TextBox
'宣告繼承自WinForm中TextBox類別
End Class
Private Sub
Form2_Load(ByVal sender
As Object,
ByVal e As
System.EventArgs) Handles
Me.Load
Dim txt1 As
New TextBox '宣告txt1
為TextBox類別
Me.Controls.Add(txt1)
'Me指作用中表單,自動加入TextBox
txt1.Multiline = True
txt1.BorderStyle = BorderStyle.FixedSingle 'TextBox外框
txt1.Left = 100
txt1.Top = 100
txt1.Width = 100
txt1.Height = 30
txt1.Text = "VB.NET"
'在txt1書寫"VB.NET"
End Sub
End
Class
下圖為本程式之執行成果

19.3.1父類別細化及子類別抽離
要探討基礎父類別與衍生子類別的繼承關係,可以兩種不同方式來觀察。如果由「上、下關係」來說,則衍生子類別為父類別的細化(Refinement),父類別細化後的衍生子類別項目特性是基礎父類別所沒有的。如以哺乳動物(Mammal
animal)為父類別為例,他們共同的特徵有:溫血、哺乳、有腳、會動、會吃、會叫……等,人類是哺乳動物中的一種,如人類視為哺乳動物的衍生類別,則人類特有的『會說話、會笑、會哭、會寫字、會計算….』等特性就是類別哺乳動物-人類類別的細化項目。
另一方面從「下、上關係」來說,是將子類別中有共同特徵,全部抽離彙集(Abstraction)起來就可以組成基礎父類別,這種抽離彙集作業亦稱之為共同化(Generalization)。以繪圖程式來說,您可以定義規範一個可以畫點、線(直線、弧)、面(矩形、多邊形、園、橢圓)、組合圖形(多種圖元組合)的畫圖類別。這些畫圖物件中如線條顏色、線寬(Pens)、塗抹顏色(Brushes),為這些類別的共同特性,因此可以將其抽離出來作為基礎類別;而所有圖元其外框都可以以矩形為邊界包含,因此其也可以被抽離出來作為基礎類別。您可以將這些共同的屬性或方法抽離後定義一個名稱為「Drawable」基礎父類別,在Drawable中定義所有圖形的邊界矩形為單一變數Rectangle,將畫圖的畫筆、筆刷各別定義成類別。同時定義DrawObject為可以畫所有圖形的方法(程序),在DrawObject畫圖程序前用MustOverrides關鍵字將DrawObject定義為抽象方法,其實作必須藉DrawObject所有衍生類別來執行完成,在Drawable類別定義其應有的參數。如此將共同物件抽離後,所有子類別間重複及共同的程式碼就可以刪除精簡,這就是利用OOP的好處。為能讓人清楚瞭解衍生類別及基礎類別的繼承關係,可以將Drawable類別的衍生類別物件名稱,在Drawable後加
寫圖元名稱如:DrawableLine、DrawableEllipse、DrawableRectangle、DrawablePolyline、DrawablePolygon,如此不但能讓人清楚瞭解其相互間繼承關係,也不會與命名空間提供的繪圖方法混淆。為方便圖元的存取,也可以在Drawable基礎類別中另定義一個DrawAllObject來存取所有畫在繪圖物件上的圖元。如下面的片段程式碼:(1)抽象基礎類別為Drawable(以關鍵字MustInherit修飾),Drawable中屬性及方法多宣告為Public,抽象基礎類別Drawable中包含抽象方法Sub
Draw及Function GetBounds (修飾字MustOverride)。(2)基礎類別DrawableAllObject。(3)衍生類別DrawableLine(繼承自Drawable),因此DrawableLine中方法Sub
Draw及Function GetBounds均必須使用關鍵字Overrides修飾相互呼應。有關抽象基礎類別Drawable相關說明及程式編碼部分請參考Rod
Stephens所著Visual Basic 2010 Programmer’s
Reference第25章。
(a)抽象基礎類別Drawable
Public MustInherit Class Drawable
‘抽象基礎類別
'繪圖屬性.
Public ForeColor As Color
Public FillColor As Color
Public LineWidth As Integer = 0
.
.
.
'
建構式.
Public Sub New()
'無引數建構式
ForeColor = Color.Black
FillColor = Color.White
End Sub
Public Sub New(ByVal fore_color As Color, ByVal fill_color As Color,
Optional ByVal line_width As Integer = 0)
'有引數建構式,以此和衍生Class對口
LineWidth = line_width
ForeColor = fore_color
FillColor = fill_color
End Sub
'
前景色屬性.
Public Property ForeColorArgb() As Integer
Get
Return ForeColor.ToArgb()
End Get
Set(ByVal Value As Integer)
ForeColor = Color.FromArgb(Value)
End Set
End Property
'背景色屬性.
Public Property FillColorArgb() As Integer
Get
Return FillColor.ToArgb()
End Get
Set(ByVal Value As Integer)
FillColor = Color.FromArgb(Value)
End Set
End Property
'
在繪圖物件上繪圖.
Public MustOverride Sub Draw(ByVal gr As Graphics)
‘抽象方法
'
矩形邊界.
Public MustOverride Function GetBounds() As Rectangle
‘抽象方法
.
.
.
End
Class
Public Property
BackColorArgb() As
Integer
(b)基礎類別DrawableAllObject
Public
Class DrawableAllObject
Public Drawables
As New
List(Of Drawable)
'
背景色The
background color.
Public BackColor
As Color = SystemColors.Control
Public Property
BackColorArgb() As
Integer
Get
Return BackColor.ToArgb()
End Get
Set(ByVal
Value As
Integer)
BackColor = Color.FromArgb(Value)
End Set
End Property
'
建構式Constructors.
Public Sub
New()
End Sub
Public Sub
New(ByVal
background_color As Color)
BackColor = background_color
End Sub
.
.
.
Public Property
SelectedDrawable() As Drawable
Get
Return m_SelectedDrawable
End Get
Set(ByVal
Value As Drawable)
'如果不是所選取的圖元者
.
If
Not (m_SelectedDrawable Is
Nothing) Then
m_SelectedDrawable.IsSelected =
False
End
If
'
選取新物件.
m_SelectedDrawable = Value
If
Not (m_SelectedDrawable Is
Nothing) Then
m_SelectedDrawable.IsSelected =
True
End
If
End Set
End Property
.
.
.
'
畫圖元物件.
Public Sub
Draw(ByVal gr
As Graphics)
'
清楚背景.
gr.Clear(BackColor)
gr.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
'
繪製物件.
For Each
dr As Drawable
In Drawables
dr.Draw(gr)
Next dr
End Sub
.
.
.
'
回傳畫圖物件邊界.
Public Function
GetBounds() As Rectangle
If Drawables.Count < 1
Then Return
(New Rectangle(0, 0, 0, 0))
Dim result As
Rectangle = Drawables(0).GetBounds()
For Each
dr As Drawable
In Drawables
result = Rectangle.Union(result, dr.GetBounds())
Next dr
Return result
End Function
End
Class
(c)衍生類別DrawableLine
Imports
System.Math
Public
Class DrawableLine
Inherits Drawable
'
建構式.
Public Sub
New()
End Sub
Public Sub
New(ByVal
fore_color As Color,
Optional ByVal
line_width As
Integer = 0, Optional
ByVal new_x1 As
Integer = 0,
Optional ByVal new_y1
As Integer
= 0, Optional
ByVal new_x2 As
Integer = 1,
Optional ByVal new_y2
As Integer
= 1) ’以此與呼叫程式(主程式溝通對口)
MyBase.New(fore_color,
Nothing, line_width)
’以此和基礎Class對口
X1
= new_x1
Y1
= new_y1
X2
= new_x2
Y2
= new_y2
End Sub
'
在繪圖物件上繪圖.
Public
Overrides Sub Draw(ByVal
gr As System.Drawing.Graphics)
‘Do
something
End Sub
'
回傳矩形邊界.
Public
Overrides Function GetBounds()
As System.Drawing.Rectangle
Return New
Rectangle( _
Min(X1, X2), _
Min(Y1, Y2), _
Abs(X2 - X1), _
Abs(Y2 - Y1))
End Function
.
.
.
.
'
移動新點.
Public
Overrides Sub NewPoint(ByVal
x As Integer,
ByVal y As
Integer)
X2
= x
Y2
= y
End Sub
'
如果是空物件回傳真值.
Public
Overrides Function IsEmpty()
As Boolean
Return (X1 = X2)
AndAlso (Y1 = Y2)
End Function
End
Class
上面所介紹之程式碼係節錄自Rod Stephens所著Visual
Basic 2010 Programmer’s
Reference第25章,原始碼艱澀難懂,也無法題提供各別圖元之x_y座標,因此筆者另編一個比較實用,可以適用在多種圖元(直線、多重線、矩形、橢圓(弧)、圓(弧)、正多邊形、星形、任意閉合多邊形等之邊碼供讀者參考。因為直線是多重線之特例,而矩形、橢圓(弧)、圓(弧)、正多邊形、星形均可視為多重線之延伸。因此只要編輯多重線之方法後,其餘只要參考或將各種圖元先轉換為多重線後直接援用多重線之方法即可。
(a)抽象基礎類別Drawable
Imports
System.Math
Imports
System.Xml.Serialization
Public
MustInherit
Class Drawable
' Drawing characteristics.
Public ForeColor
As Color
Public FillColor
As Color
Public LineWidth
As Integer
= 0
Public LineStyle
As Integer
= 0
Public PenUse
As Pen
Public BrushUse
As Brush
Public IsSelected
As Boolean
= False
' Constructors.
Public Sub
New()
ForeColor = Color.Black
FillColor = Color.White
End Sub
Public Sub
New(ByVal
fore_color As Color,
ByVal fill_color
As Color,
Optional ByVal line_width
As Integer
= 0, Optional
ByVal line_style As
Integer = 0,
Optional ByVal isClosed
As Boolean
= False,
Optional ByVal m_brushUse
As Brush =
Nothing, Optional
ByVal isFillRgn
As Boolean =
False) 'only
pass properties
LineWidth
= line_width
ForeColor = fore_color
FillColor = fill_color
LineStyle = line_style
BrushUse = m_brushUse
End Sub
' Property procedures to serialize and
' deserialize ForeColor and FillColor.
Public Property
ForeColorArgb() As
Integer
Get
Return ForeColor.ToArgb()
End Get
Set(ByVal
Value As
Integer)
ForeColor = Color.FromArgb(Value)
End Set
End Property
Public Property
FillColorArgb() As
Integer
Get
Return FillColor.ToArgb()
End Get
Set(ByVal
Value As
Integer)
FillColor = Color.FromArgb(Value)
End Set
End Property
#Region
"Methods to override"
' Draw the object on this Graphics surface.
Public
MustOverride Sub Draw(ByVal
gr As Graphics)
' Return the object's bounding rectangle.
Public
MustOverride Function GetLimit()
As Rectangle
' Return True if this point is on the object.
Public
MustOverride Function IsAt(ByVal
ptTest As Point)
As Boolean
' Return True if the object is empty (e.g. a
zero-length line).
Public
MustOverride Function IsEmpty()
As Boolean
#End
Region
End
Class
(b)衍生類別DrawableLine
Imports
System.Math
Imports
System.Drawing
Imports
System.Drawing.Drawing2D
Imports
System.Xml.Serialization
Public
Class DrawablepolyLine
Inherits Drawable
' Constructors.
Public Sub
New()
End Sub
Private m_Pts()
As PointF, npt As
Integer, m_Canvas
As PictureBox, m_isClosed
As Boolean,
m_Pen As Pen, m_brushStyle
As Integer,
m_Brush As Brush
Public Sub
New(ByVal
gr As Graphics,
ByVal fore_color As Color,
ByVal fill_color
As Color, ByVal
line_width As
Integer, ByVal ptsIn()
As PointF, _
Optional
ByVal isClosed
As Boolean =
False, Optional
ByVal brush_style
As Integer
= -1, Optional
ByVal line_style As
Integer = 0)
MyBase.New(fore_color, fill_color,
line_width, isClosed)
Dim stGeoXyz As
String = ""
m_isClosed = isClosed
m_brushStyle = brush_style
If m_brushStyle = -1
Then m_Brush =
New SolidBrush(fill_color)
If m_brushStyle = 0
Then m_Brush =
New HatchBrush(HatchStyle.Cross, fore_color)
npt = UBound(ptsIn)
If npt = 0 Then
glPolyline.Pts(0) = ptsIn(0)
If npt = 0 Then
glPolyline.Pts(1) = ptsIn(0)
For i As
Integer = 0 To
npt
ReDim
Preserve m_Pts(i), glPolyline.Pts(i)
m_Pts(i) = ptsIn(i)
glPolyline.Pts(i) = ptsIn(i)
Next i
LineLimit = getLimitLine(m_Pts)
Draw(gr)
'圖元資料以Collection集合儲存
Dim stGeoProp
As String
stGeoProp = Trim("polyline" &
"," &
ColorTranslator.ToWin32(fore_color) & ","
& ColorTranslator.ToWin32(fore_color) & ","
& line_width & "," & line_style)
collsGeoProp.Add(stGeoProp)
stGeoXyz = Trim("polyline" &
stGeoXyz)
CollsGeoXyz.Add(stGeoXyz)
End Sub
Public Sub
New(ByVal
canvasIn As PictureBox,
ByVal fore_color
As Color, ByVal
fill_color As Color,
ByVal line_width
As Integer,
ByVal ptsIn()
As PointF, Optional
ByVal isClosed
As Boolean =
False)
m_isClosed = isclosed
npt = UBound(ptsIn)
If npt = 0 Then
glPolyline.Pts(0) = ptsIn(0)
If npt = 0 Then
glPolyline.Pts(1) = ptsIn(0)
For i As
Integer = 0 To
npt
ReDim
Preserve m_Pts(i)
m_Pts(i) = ptsIn(i)
Next i
m_Canvas = canvasIn
If m_isClosed =
True Then
Dim highlight_brush
As Brush = New
SolidBrush(FillColor)
m_Canvas.CreateGraphics.FillPolygon(highlight_brush, m_Pts)
Else
m_Canvas.CreateGraphics.DrawLines(New
Pen(fore_color, line_width), m_Pts)
End If
End Sub
' Draw the object on this Graphics surface.
Public
Overrides Sub Draw(ByVal
gr As Graphics)
On Error
Resume Next
If m_isClosed =
True Then
'Dim highlight_brush As Brush =
New SolidBrush(FillColor)
gr.FillPolygon(m_Brush, m_Pts)
Else
Dim highlight_pen
As New
Pen(ForeColor, LineWidth)
gr.DrawLines(highlight_pen, m_Pts)
End If
End Sub
'------------------------------
Public
Overrides Function Getlimit()
As System.Drawing.Rectangle
Return New
Rectangle( _
CInt(Min(LineLimit.Pt0.X,
LineLimit.Pt0.X)), _
CInt(Min(LineLimit.Pt0.Y,
LineLimit.Pt1.Y)), _
CInt(Abs(LineLimit.Pt1.X -
LineLimit.Pt0.X)), _
CInt(Abs(LineLimit.Pt1.Y -
LineLimit.Pt0.Y)))
End Function
' Return True if this point is on the object.
Public
Overrides Function IsAt(ByVal
PtIn As Point)
As Boolean
Return PointNearSegment(PtIn,
New Point(CInt(LineLimit.Pt0.X),
CInt(LineLimit.Pt0.Y)),
New Point(CInt(LineLimit.Pt1.X),
CInt(LineLimit.Pt1.Y)))
End Function
' Move the second point.
' Return True if the object is empty (e.g. a
zero-length line).
Public
Overrides Function IsEmpty()
As Boolean
Return Abs(LineLimit.Pt0.X -
LineLimit.Pt1.X) <= 1 AndAlso
Abs(LineLimit.Pt0.Y - LineLimit.Pt1.Y) <= 1
End Function
End
Class
(b)衍生類別DrawablePolyLine
Imports
System.Math
Imports
System.Drawing
Imports
System.Drawing.Drawing2D
Imports
System.Xml.Serialization
Public
Class DrawablepolyLine
Inherits Drawable
' Constructors.
Public Sub
New()
End Sub
Private m_Pts()
As PointF, npt As
Integer, m_Canvas
As PictureBox, m_isClosed
As Boolean,
m_Pen As Pen, m_brushStyle
As Integer,
m_Brush As Brush
Public Sub
New(ByVal
gr As Graphics,
ByVal fore_color As Color,
ByVal fill_color
As Color, ByVal
line_width As
Integer, ByVal ptsIn()
As PointF, _
Optional
ByVal isClosed
As Boolean =
False, Optional
ByVal brush_style
As Integer
= -1, Optional
ByVal line_style As
Integer = 0)
MyBase.New(fore_color, fill_color,
line_width, isClosed)
Dim stGeoXyz As
String = ""
m_isClosed = isClosed
m_brushStyle = brush_style
If m_brushStyle = -1
Then m_Brush =
New SolidBrush(fill_color)
If m_brushStyle = 0
Then m_Brush =
New HatchBrush(HatchStyle.Cross, fore_color)
npt = UBound(ptsIn)
If npt = 0 Then
glPolyline.Pts(0) = ptsIn(0)
If npt = 0 Then
glPolyline.Pts(1) = ptsIn(0)
For i As
Integer = 0 To
npt
ReDim
Preserve m_Pts(i), glPolyline.Pts(i)
m_Pts(i) = ptsIn(i)
glPolyline.Pts(i) = ptsIn(i)
Next i
LineLimit = getLimitLine(m_Pts)
'畫永久圖元時使用
Draw(gr)
'圖元資料以Collection集合儲存
Dim stGeoProp
As String
stGeoProp = Trim("polyline" &
"," &
ColorTranslator.ToWin32(fore_color) & ","
& ColorTranslator.ToWin32(fore_color) & ","
& line_width & "," & line_style)
collsGeoProp.Add(stGeoProp)
stGeoXyz = Trim("polyline" &
stGeoXyz)
CollsGeoXyz.Add(stGeoXyz)
End Sub
Public Sub
New(ByVal
canvasIn As PictureBox,
ByVal fore_color
As Color, ByVal
fill_color As Color,
ByVal line_width
As Integer,
ByVal ptsIn()
As PointF, Optional
ByVal isClosed
As Boolean =
False)
m_isClosed = isclosed
npt = UBound(ptsIn)
If npt = 0 Then
glPolyline.Pts(0) = ptsIn(0)
If npt = 0 Then
glPolyline.Pts(1) = ptsIn(0)
For i As
Integer = 0 To
npt
ReDim
Preserve m_Pts(i)
m_Pts(i) = ptsIn(i)
Next i
'畫臨時性元時使用(如橡皮筋線)
m_Canvas = canvasIn
If m_isClosed =
True Then
Dim highlight_brush
As Brush = New
SolidBrush(FillColor)
m_Canvas.CreateGraphics.FillPolygon(highlight_brush, m_Pts)
Else
m_Canvas.CreateGraphics.DrawLines(New
Pen(fore_color, line_width), m_Pts)
End If
End Sub
' Draw the object on this Graphics surface.
Public
Overrides Sub Draw(ByVal
gr As Graphics)
On Error
Resume Next
If m_isClosed =
True Then
'Dim highlight_brush As Brush =
New SolidBrush(FillColor)
gr.FillPolygon(m_Brush, m_Pts)
Else
Dim highlight_pen
As New
Pen(ForeColor, LineWidth)
gr.DrawLines(highlight_pen, m_Pts)
End If
End Sub
'------------------------------
Public
Overrides Function Getlimit()
As System.Drawing.Rectangle
Return New
Rectangle( _
CInt(Min(LineLimit.Pt0.X,
LineLimit.Pt0.X)), _
CInt(Min(LineLimit.Pt0.Y,
LineLimit.Pt1.Y)), _
CInt(Abs(LineLimit.Pt1.X -
LineLimit.Pt0.X)), _
CInt(Abs(LineLimit.Pt1.Y -
LineLimit.Pt0.Y)))
End Function
' Return True if this point is on the object.
Public
Overrides Function IsAt(ByVal
PtIn As Point)
As Boolean
Return PointNearSegment(PtIn,
New Point(CInt(LineLimit.Pt0.X),
CInt(LineLimit.Pt0.Y)),
New Point(CInt(LineLimit.Pt1.X),
CInt(LineLimit.Pt1.Y)))
End Function
' Move the second point.
' Return True if the object is empty (e.g. a
zero-length line).
Public
Overrides Function IsEmpty()
As Boolean
Return Abs(LineLimit.Pt0.X -
LineLimit.Pt1.X) <= 1 AndAlso
Abs(LineLimit.Pt0.Y - LineLimit.Pt1.Y) <= 1
End Function
End
Class
(c)呼叫程式碼
Dim
m_NewDrawable As Drawable
Dim
myBufferBitmap As Bitmap
Dim
myBufferGraphics As Graphics
m_NewDrawable = New
DrawablepolyLine(PicDraw, Color.White, Color.Red, 5, ptsIn)
m_NewDrawable = New
DrawablepolyLine(myBufferGraphics , Color.Black, Color.Red, 5, ptsIn)

m_NewDrawable = New
DrawablepolyLine(myBufferGraphics, Color.Red, Color.Red, 5, ptsIn,
True, _ 0)

細化及抽離作業應該適度,過或不及都不好,過度細化會讓程式混亂不易瞭解及維護;相反過度抽離會讓程式的基礎類別龐大容易造成錯誤。
19.3.2「是一種」與「有一個」的觀念
「細化」及「抽離」為類別繼承關係的重要觀念及技巧,「是一種(Is_a)」與「有一個(Has_a」的觀念,則能幫助User瞭解「細化」及「抽離」關係。Is_a是意指物件是某類別的特殊形式。舉例而言,人類(是動物一種,屬衍生類別)是哺乳動物(基礎類型)「是一種(Is_a)」特別類型,所以人類是哺乳動物(基礎類型)的衍生類別,這是Is_a的關係;Has_a關係則為某類別擁有某些項目作為屬性。如一般公司僱員多擁「有一個(Has_a」姓名、性別、年齡、教育程度、地址、薪俸等屬性,經理人則是僱員中的成員之一,因此僱員屬性是從經理人中抽離後的共同交集屬性,這是Has_a的關係。
19.3.3覆寫及遮蓋
在衍生類別增加類別的屬性、方法及事件是一種很容易的事情,您只要在衍生類別宣告就好。在基礎類別clsBase中有Public
Function Add(),衍生類別clsDerived中也有Public
Function Add(),二次衍生類別clsMorederived有Public
Function Add()。基礎類別以關鍵字Overridable修飾,衍生類別及二次衍生類別均以關鍵字Overrides修飾。在主程式呼叫引用方法Add()後,基礎類別bas1.add(1,2)=3,衍生類別被覆寫後變成減法,bas2.add(1,2)=-1,二次衍生類別被覆寫後變成乘法,bas3.add(1,2)=2
。注意,所有可以被覆寫及覆寫的方法、屬性、事件的存取範圍宣告均必須為Public。
(a)類別程式碼
Public
Class clsBase
Public
Overridable Function Add(ByVal
a As Double,
ByVal b As
Double) As
Double
Return a + b
'加法
End Function
End Class
Public Class
clsDerived
Inherits clsBase
Public
Overrides Function Add(ByVal
a As Double,
ByVal b As
Double) As
Double
'請將Private
Shadows改為Public Shadows重新測試看看結果
Return a - b
'減法
End Function
End Class
Public Class
clsMorederived
Inherits clsDerived
Public
Overrides Function Add(ByVal
a As Double,
ByVal b As
Double) As
Double
'請將Private
Shadows改為Public Shadows重新測試看看結果
Return a * b
'乘法
End Function
End Class
(b)主程式程式碼
Dim bas1 As
New clsBase
Dim bas2 As
New clsDerived
Dim bas3 As
New clsMorederived
Dim retval1 As
Double = bas1.Add(1, 2)
ListBox1.Items.Add("bas1.add(1,2)= "
& retval1)
Dim retval2 As
Double = bas2.Add(1, 2)
ListBox1.Items.Add("bas2.add(1,2)= "
& retval2)
Dim retval3 As
Double = bas3.Add(1, 2)
ListBox1.Items.Add("bas3.add(1,2)= "
& retval3)
如下面的程式碼,基礎類別Base中有公有函數Add(),衍生類別Derived中也有公有函數Add(),二次衍生類別Morederived也有公有函數Add()。如在主程式中引用Class
Base,Derived及Morederived後所得答案,因衍生類別Derived及Morederived中之Function
Add均均以Private Shadows宣告,故均採用基礎類別的函數Add(1,2),故所有答案均為3,縱使有Shadows關鍵字亦不會被遮蓋。
‘(a)類別程式碼
Public
Class Base
Public Function
Add(ByVal a As
Double, ByVal
b As Double)
As Double
Return a + b
'加法
End Function
End
Class
Public
Class Derived
Inherits Base
Private Shadows
Function Add(ByVal
a As Double,
ByVal b As
Double) As
Double
‘請將Private
Shadows改為PublicShadows重新測試看看結果
Return
a - b '減法
End Function
End
Class
Public
Class Morederived
Inherits Derived
Private Shadows
Function Add(ByVal
a As Double,
ByVal b As
Double) As
Double
‘請將Private
Shadows改為PublicShadows重新測試看看結果
Return a * b
'乘法
End Function
End
Class
‘(b)主程式程式碼
Private
Sub btnShadows1_Click(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles
btnShadows1.Click
Dim
bas1 As New
Base
Dim bas2 As
New Derived
Dim bas3 As
New Morederived
Dim retval1 As
Double = bas1.Add(1, 2)
ListBox1.Items.Add("bas1.add(1,2)= "
& retval1)
Dim retval2 As
Double = bas2.Add(1, 2)
ListBox1.Items.Add("bas2.add(1,2)= "
& retval2)
Dim retval3 As
Double = bas3.Add(1, 2)
ListBox1.Items.Add("bas3.add(1,2)= "
& retval3)
End Sub
如將衍生類別Derived及Morederived中之Function
Add如改均以Public Shadows宣告,則物件Bas1,bas2,bas3均採用自己的函數方法不會被遮蓋,故答案分別為3(加法),-1(減法),2(乘法)。
19.3.4介面繼承
實作(Implements)關鍵字表示本類別有提供實作為介面(Interface)。一個介面為定義規範實作類別必須提供的行為[屬性或(及)方法],但不必提供此等行為的實作。如下面的類別clsInterface中問候介面(Igreeting,習慣上介面多以大寫I開頭),定義了問候介面的方法Sub
GoodMorning()及Sub GoodEvening(ByVal
CurrentDate As DateTime)。在問候介面Igreeting
前有關鍵字InterFace修飾。類別clsInterface中包夾兩個類別Class
English及Class Chinese
(a)類別程式碼
Public
Class clsInterface
Interface Igreeting
Sub GoodMorning()
'不需要存取範圍的關鍵字
Sub GoodEvening(ByVal
CurrentDate As DateTime) '不需要存取範圍的關鍵字
End Interface
Public Class English
Implements Igreeting
Public Sub
GoodMorning() Implements
Igreeting.GoodMorning
Form1.ListBox1.Items.Add("Good
morning!")
End Sub
Public Sub
GoodEvening(ByVal CurrentDate
As DateTime)
Implements Igreeting.GoodEvening
Form1.ListBox1.Items.Add("Good
evening! Now it is :" & CurrentDate)
End Sub
End Class
Public Class Chinese
Implements Igreeting
Public Sub
GoodMorning() Implements
Igreeting.GoodMorning
Form1.ListBox1.Items.Add("現在時間為:"
& Now())
Form1.ListBox1.Items.Add("您好!早安")
End Sub
Public Sub
GoodEvening(ByVal CurrentDate
As DateTime)
Implements Igreeting.GoodEvening
Form1.ListBox1.Items.Add("您好!晚安
, 現在時間為: "
& CurrentDate)
End Sub
End Class
(b)主程式程式碼
Dim
Hello As New
clsInterface.English '建構介面類別clsInterfac的英文物件
Dim Hi As
New clsInterface.Chinese
'建構介面類別clsInterfac的中文物件
Hello.GoodMorning() '實作英文早安問候
Hello.GoodEvening(Now()) '實作英文晚安問候
Hi.GoodMorning() '實作中文早安問候
Hi.GoodEvening(Now()) '實作中文晚安問候
類別Class
English 及Class Chinese 下一列均有陳述句Implements
Igreeting。在早安GoodMorning()及晚安GoodEvening()問候Sub方法後面均跟隨有關鍵字Implements及對應的介面所定義的Sub名稱。類別中類別Class
English 及Class Chinese中均有可以實作的程式碼。
正因為實作介面只提供行為規範,並不提供實作內容,實用上沒有繼承方便,如非必要應儘量使用繼承,此因為繼承可以保有父類別的屬性及方法等。但VB.NET並不支援子類別同時繼承多個父類別,因此如類別需繼承多個父類別的屬性及方法時,就需要使用實作介面。
19.4多態或多型
多態或多型(Polymorphism)為類別可以同時使用不同個數的引數及資料型態,以定義多個相同名稱的屬性、方法、及事件。在同一Class中,可以使用相同名稱的方法(Sub或Function),只要方法中成員所使用參數不同即可,此即為方法多載(Method
overload)。如下面的類別Public Class ClsPhoneCall程式碼基礎類別Public
Class ID中提供Public Sub New(ByVal Number As
String)及Public Overridable Sub Dial(),衍生類別Public
Class Phone及二次衍生類別Public Class CreditCardID,均提供方法Public
Sub New(ByVal Number As String)及Public
Overrides Sub Dial(),其中Dial()均有覆寫關鍵字Overrides,因此衍生類別及二次衍生類別物件引用Dial()方法都採用覆寫後的Dial()方法,同樣一個類別可以同時定義不同的Dial()方法,此即為類別的多態或多型。
(a)類別程式碼
Public
Class ClsPhoneCall
Public Class
ID '基礎類別,身份類別
Public Number
As String
Protected stTpt
As String
Public Sub
New(ByVal
Number As
String) '建構式
Me.Number = Number
'自己呼叫自己用Me
End Sub
Public
Overridable Sub Dial()
'可以被覆寫
stTpt = "撥號:
"
& Number
Form6.ListBox1.Items.Add(stTpt)
End Sub
End Class
Public Class
Phone '衍生類別,電話類別
Inherits ID
Public Sub
New(ByVal
Number As
String) '建構式
MyBase.New(Number)
'在衍生類別呼叫基礎類別中的方法,屬性,事件用MyBase
End Sub
Public
Overrides Sub Dial()
'覆寫
stTpt = "按鈕電話:
"
& Number
Form6.ListBox1.Items.Add(stTpt)
End Sub
End Class
Public Class
CreditCardID '二次衍生類別,信用卡類型
Inherits ID
Public Sub
New(ByVal
Number As
String) '建構式
MyBase.New(Number)
'在衍生類別呼叫基礎類別中的方法,屬性,事件用MyBase
End Sub
Public
Overrides Sub Dial()
'覆寫
stTpt = "撥號電號:
"
& Number
Form6.ListBox1.Items.Add(stTpt)
End Sub
End Class
End
Class
(b)主程式程式碼
Dim
card As New
ClsPhoneCall.CreditCardID("統一編號A1234-5678")
'建構二次衍生類別物件
Dim phone As
New ClsPhoneCall.Phone("電話號碼02-1234-5678")
'建構衍生類別物件
ListBox1.Items.Clear()
ListBox1.Items.Add("使用標準物件")
card.Dial() '實作二次衍生類別Dial()方法
phone.Dial() '實作衍生類別Dial()方法
Dim PolyID As
ClsPhoneCall.ID '設定基礎類別的物件
ListBox1.Items.Add("使用多態電話物件")
PolyID = card '重新設定基礎類別物件為二次衍生類別物件
PolyID.Dial()
PolyID = phone '重新設定基礎類別物件為衍生類別物件
PolyID.Dial()
19.5建構式
建構式或建構子(Constructor)為Class與外部溝通的對話窗口,在呼叫程式中以New
關鍵字與Class中New程式陳述句呼應以執行程式的初始化工作。
(a)類別程式碼
Public
Class ClsConstructor
Public Class
Point
Private mX, mY
As Double
Public Sub
New()'無引數之次程序
End Sub
Public Sub
New(ByVal
xValue As
Double, _
ByVal yValue As
Double) '有引數之次程序,定義點類別為配對倍精準浮點數
MsgBox("X,Y= " & xValue &
";" & yValue)
mX = xValue
mY = yValue
End Sub
Public
Overrides Function ToString()
As String
Return
"(" & mX &
", " & mY
& ")"
MsgBox("in class Point:
Overrides Function Point(" & mX.ToString() &
", " & mY.ToString()
& ")")
End Function
'
覆寫為字串
End Class
Public Class
Circle
Inherits Point
'繼承點類此
Private mRadius
As Double
Public Sub
New()
End Sub
Public Sub
New(ByVal
xValue As
Double, _
ByVal yValue
As Double,
ByVal radValue
As Double)
'定義圓類別為配對倍精準浮點數
MyBase.New(xValue, yValue)
'繼承自點類別
Form1.ListBox1.Items.Add("In
class Circle: X,Y,R= " & xValue & ";"
& yValue & ";" & radValue)
mRadius = radValue
End
Sub ' New
Public
Overrides Function ToString()
As String
Return
"圓心=
"
& MyBase.ToString() & _
";
半徑
= "
& mRadius
Form1.ListBox1.Items.Add("In
class Circle: R= " & mRadius)
End Function
' ToString
End Class
End
Class
下面程式碼(主程式)利用建構式與類別ClsConstructor對口
(b)主程式程式碼
Dim
Point1, Point2 As ClsConstructor.Point
Dim Circle1, Circle2
As ClsConstructor.Circle
Point1 = New ClsConstructor.Point(50.0F,
50.0F)
'與ClsConstructor.Point()的Sub
New()對口
Circle1 = New
ClsConstructor.Circle(120.0, 89.0, 3.7)
'與ClsConstructor..Circle()的Sub
New()對口
ListBox1.Items.Clear()
ListBox1.Items.Add("點1:
"
& point1.ToString() & _
vbCrLf & "圓1:
"
& circle1.ToString())
point2 = circle1
ListBox1.Items.Add("圓1
(via 點2): "
& point2.ToString())
Circle2 = CType(Point2,
ClsConstructor.Circle) '
點point2轉換為圓
ListBox1.Items.Add("圓1
(via 圓2): "
& circle2.ToString())
If (TypeOf
point1 Is ClsConstructor.Circle)
Then
Circle2 = CType(Point1,
ClsConstructor.Circle) '點point1轉換為圓
ListBox1.Items.Add("點參考到圓")
Else
ListBox1.Items.Add("點未參考到圓"
End If
|