VB.NET Module 筆記(基礎篇)
Module 是 VB.NET 中用來集中放置共用成員的結構。它不需要先建立物件,就能直接呼叫其中的函式、程序、常數或屬性,因此常用來放置工具方法、固定規則、格式化函式與擴充方法。
Module 的重點不是把程式碼集中丟到同一個地方,而是放置「不依附某個物件狀態」的共用邏輯。若功能需要保存每個物件自己的資料,或需要建立多個不同實例,就應優先考慮使用 Class。
先理解 Module 在做什麼
Module 放的是共用功能,不是物件狀態
Module 內的成員具有共享性質。呼叫時不需要 New,通常直接寫 ModuleName.FunctionName()。因此,它很適合放沒有個別生命週期的功能。
- 共用工具:文字整理、格式化、代碼產生。
- 固定規則:費用試算、檢查規則、顯示轉換。
- 共用常數:系統名稱、固定費率、固定上限。
- 擴充方法:讓既有型別多出更順手的呼叫方式。
Module 適合的判斷方式:若一段功能不需要保存自己的獨立資料,也不需要建立多份實例,就可以考慮放進 Module。若每筆資料都有自己的狀態,例如一張會員卡、一筆訂單、一個計時器,就比較適合使用 Class。
基本語法
Public Module 模組名稱
Public Const 固定值名稱 As String = "固定內容"
Public Function 共用函式名稱() As String
Return "共用結果"
End Function
End Module
Module 成員與存取範圍
| 修飾詞 | 可存取範圍 | 常見用途 |
|---|---|---|
| Public | 專案其他地方可存取。 | 共用函式、共用常數。 |
| Private | 只在同一個 Module 內使用。 | 輔助方法、內部整理邏輯。 |
| Friend | 同一組件內可存取。 | 同專案內部工具,不公開到外部組件。 |
Module 不是萬用收納盒
若把所有函式、全域變數與畫面流程都放進同一個 Module,程式短期看似方便,長期會變得難以追蹤。Module 應保持明確主題,例如格式化、驗證、費用規則或擴充方法。
常數與固定規則
場景二:寄物櫃費用試算
固定費率與計算規則若會被多個表單使用,可以集中放在 Module。這樣規則修改時,不必到各個按鈕事件中逐一尋找。
需要的主控項
TextBoxHours:輸入使用小時數。ButtonCalculateFee:計算寄物費。LabelFee:顯示結果。
範例程式碼
Public Module LockerFeeRules
Public Const FirstHourFee As Decimal = 20D
Public Const ExtraHourFee As Decimal = 10D
Public Function CalculateFee(ByVal hours As Integer) As Decimal
If hours <= 0 Then
Return 0D
End If
If hours = 1 Then
Return FirstHourFee
End If
Return FirstHourFee + (hours - 1) * ExtraHourFee
End Function
End Module
Public Class Form1
Private Sub ButtonCalculateFee_Click(sender As Object, e As EventArgs) Handles ButtonCalculateFee.Click
Dim hours As Integer
If Not Integer.TryParse(TextBoxHours.Text.Trim(), hours) Then
LabelFee.Text = "請輸入使用小時數"
Return
End If
Dim fee As Decimal = LockerFeeRules.CalculateFee(hours)
LabelFee.Text = "寄物費用:" & fee.ToString("N0") & " 元"
End Sub
End Class
邏輯解析
FirstHourFee與ExtraHourFee是固定規則,適合宣告成常數。CalculateFee不需要保存使用者狀態,只依照傳入小時數計算。- 多個表單若都要計算寄物費,可共用同一個 Module。
Public 函式與 Private 輔助方法
場景三:活動名牌顯示文字
Module 可以公開主要函式,同時把細節整理成 Private 輔助方法。外部只需要知道要呼叫哪個公開函式,不需要知道內部如何清理姓名或轉換組別代碼。
需要的主控項
TextBoxGuestName:輸入姓名。TextBoxGroupCode:輸入組別代碼。ButtonBuildBadge:建立名牌文字。LabelBadge:顯示結果。
範例程式碼
Public Module BadgeTextRules
Public Const EventTitle As String = "城市小講堂"
Public Function BuildBadgeText(ByVal guestName As String, ByVal groupCode As String) As String
Dim safeName As String = CleanGuestName(guestName)
Dim safeGroup As String = NormalizeGroupCode(groupCode)
Return EventTitle & "|" & safeName & "|" & safeGroup
End Function
Private Function CleanGuestName(ByVal guestName As String) As String
If String.IsNullOrWhiteSpace(guestName) Then
Return "未填姓名"
End If
Return guestName.Trim()
End Function
Friend Function NormalizeGroupCode(ByVal groupCode As String) As String
If String.IsNullOrWhiteSpace(groupCode) Then
Return "GENERAL"
End If
Return groupCode.Trim().ToUpper()
End Function
End Module
Public Class Form1
Private Sub ButtonBuildBadge_Click(sender As Object, e As EventArgs) Handles ButtonBuildBadge.Click
LabelBadge.Text = BadgeTextRules.BuildBadgeText(TextBoxGuestName.Text, TextBoxGroupCode.Text)
End Sub
End Class
邏輯解析
BuildBadgeText是外部主要呼叫入口。CleanGuestName是 Private,只在 Module 內部使用。NormalizeGroupCode是 Friend,表示同一專案組件內可使用。- 把內部細節藏起來,可讓表單端只專注於輸入與顯示。
驗證工具 Module
場景四:取件代碼格式檢查
驗證規則如果不依附特定物件,可以放在 Module 中集中管理。這個範例檢查取件代碼是否為 P 開頭,後面接 6 個數字。
需要的主控項
TextBoxPickupCode:輸入取件代碼。ButtonCheckCode:檢查代碼。LabelCodeResult:顯示檢查結果。
範例程式碼
Public Module PickupCodeRules
Public Function IsValidPickupCode(ByVal pickupCode As String) As Boolean
If String.IsNullOrWhiteSpace(pickupCode) Then
Return False
End If
Dim cleanCode As String = pickupCode.Trim().ToUpper()
If cleanCode.Length <> 7 Then
Return False
End If
If Not cleanCode.StartsWith("P") Then
Return False
End If
Dim numberPart As String = cleanCode.Substring(1)
For Each ch As Char In numberPart
If Not Char.IsDigit(ch) Then
Return False
End If
Next
Return True
End Function
End Module
Public Class Form1
Private Sub ButtonCheckCode_Click(sender As Object, e As EventArgs) Handles ButtonCheckCode.Click
If PickupCodeRules.IsValidPickupCode(TextBoxPickupCode.Text) Then
LabelCodeResult.Text = "取件代碼格式正確"
Else
LabelCodeResult.Text = "取件代碼格式錯誤"
End If
End Sub
End Class
邏輯解析
IsValidPickupCode只依照傳入文字回傳 True 或 False。- 驗證規則集中在 Module 中,其他表單也能重複使用。
- 若驗證需要查資料庫或依賴不同環境設定,就不一定適合直接放在簡單 Module。
擴充方法必須放在 Module
場景五:日期顯示擴充方法
VB.NET 的擴充方法需要宣告在 Module 中,並搭配 <Extension()>。這個範例替 Date 增加一個顯示取件日期的格式方法。
需要的主控項
DateTimePickerPickup:選擇取件日期。ButtonShowDate:顯示取件日期。LabelDate:顯示結果。
範例程式碼
Imports System.Runtime.CompilerServices
Public Module DateDisplayExtensions
<Extension()>
Public Function ToPickupLabel(ByVal value As Date) As String
Return value.ToString("MM/dd") & "(" & GetWeekText(value.DayOfWeek) & ")"
End Function
Private Function GetWeekText(ByVal day As DayOfWeek) As String
Select Case day
Case DayOfWeek.Monday
Return "一"
Case DayOfWeek.Tuesday
Return "二"
Case DayOfWeek.Wednesday
Return "三"
Case DayOfWeek.Thursday
Return "四"
Case DayOfWeek.Friday
Return "五"
Case DayOfWeek.Saturday
Return "六"
Case Else
Return "日"
End Select
End Function
End Module
Public Class Form1
Private Sub ButtonShowDate_Click(sender As Object, e As EventArgs) Handles ButtonShowDate.Click
Dim pickupDate As Date = DateTimePickerPickup.Value.Date
LabelDate.Text = "取件日期:" & pickupDate.ToPickupLabel()
End Sub
End Class
邏輯解析
- 擴充方法需要
Imports System.Runtime.CompilerServices。 <Extension()>讓Date可以使用ToPickupLabel()這種呼叫方式。- 擴充方法本身放在 Module 中,但使用時看起來像原本型別的方法。
Module 與 Class 的選擇
| 需求 | 適合 Module | 適合 Class |
|---|---|---|
| 是否需要 New | 不需要建立物件。 | 需要建立多個實例。 |
| 是否保存狀態 | 不保存個別狀態,只做共用處理。 | 每個物件有自己的資料。 |
| 是否需要繼承 | 不適合繼承設計。 | 可搭配繼承、多型與介面。 |
| 常見用途 | 格式化、驗證、固定規則、擴充方法。 | 會員、訂單、設備、工作項目、計時器。 |
場景六:計時器應使用 Class 而不是 Module
若需求是同時管理多個計時器,每個計時器都需要保存自己的開始時間,就不適合使用 Module。這種情境應使用 Class 建立多個獨立物件。
範例程式碼
Public Class SimpleTimer
Public ReadOnly Property TimerName As String
Public ReadOnly Property StartedAt As DateTime
Public Sub New(ByVal timerName As String)
Me.TimerName = timerName
Me.StartedAt = DateTime.Now
End Sub
Public Function GetElapsedSeconds() As Integer
Return CInt((DateTime.Now - StartedAt).TotalSeconds)
End Function
End Class
邏輯解析
- 每個
SimpleTimer都有自己的StartedAt。 - 若用 Module 保存開始時間,只能共用同一份狀態,無法自然表示多個計時器。
- 需要多個獨立實例時,Class 比 Module 更合適。
實務判斷與常見誤區
常見問題整理
- 把 Module 當全域變數倉庫:共用變數越多,越難追蹤資料在哪裡被改掉。
- 所有工具都塞同一個 Module:應依主題拆分,例如文字、費用、驗證、日期顯示。
- 把有狀態的資料放進 Module:若資料應該屬於某個實例,應改用 Class。
- 忽略命名:
Helper、Utility太籠統,建議使用能看出責任的名稱。 - 過度依賴共享成員:會降低測試與替換彈性。
命名建議:Module 名稱最好直接說明用途,例如 PickupCodeRules、LockerFeeRules、DateDisplayExtensions。名稱越明確,越不容易變成雜物箱。
重點整理
Module可集中放置不需要建立物件的共用成員。- Module 內的成員具有共享性質,通常可直接用模組名稱呼叫。
- 格式化、驗證、固定規則與擴充方法很適合放在 Module。
Public成員可給外部使用,Private成員只在 Module 內部使用。Friend適合同一組件內共用,但不公開到外部組件。- 擴充方法需要放在 Module 中,並使用
<Extension()>。 - Module 不適合保存大量可變共享狀態。
- 需要建立多個獨立物件、保存實例資料或設計繼承時,應使用
Class。