VB.NET Module 筆記 (基礎篇)
Module 就像是程式設計中的共享工具箱Module 可以包含各種共享的變數、函式、常數等,就像一個裝著常用工具的箱子,需要的時候隨時可以拿出來用。,它提供了一種方便的方式來組織和共享程式碼。透過瞭解 Module 的特性和使用方法,可以讓程式設計更加簡潔、易讀、易於維護。
認識 Module
Module: Module 是一種特殊的類別,它可以包含變數、常數、函式等成員,但不能建立物件實體。Module 的成員可以在整個專案中直接存取,不需要透過物件實體。它通常用來放置一些共用的公用程式函式或共享資料。
Module 有以下幾個重要特性:
- 不可建立實體Module 就像是一個共享的工具箱,不能建立多個實體,但可以直接使用裡面的工具。同樣地,Module 不能建立物件實體,但可以直接使用其中的成員。:Module 不能使用 New 關鍵字建立物件實體,它的成員都是共享的。
- 不能繼承Module 就像是一個獨立的工具箱,不能把一個工具箱放到另一個工具箱裡面。同樣地,Module 不能被其他類別繼承,也不能繼承其他類別。:Module 不能被其他類別繼承,也不能繼承其他類別。
- 共享成員Module 裡的工具可以被大家共用,不需要每個人都買一套。同樣地,Module 的成員可以在整個專案中共享,不需要透過物件實體存取。:Module 的成員可以在整個專案中直接存取,不需要透過物件實體。
- 預設為 FriendModule 就像是一個內部的工具箱,外部的人不能直接使用裡面的工具。同樣地,Module 的成員預設只能在同一個組件內存取,如果要讓其他組件存取,需要使用 Public 修飾。:Module 的成員預設存取範圍為 Friend,只能在同一個組件內存取。如果要讓其他組件存取,需要使用 Public 修飾。
Module 通常用在以下場景:
- 存放共用的公用程式函式,例如字串處理、日期轉換等。
- 存放共享的常數或變數,例如應用程式設定、全域資料等。
- 提供擴充方法,為現有的類別添加新的功能。
使用 Module 可以讓程式碼更加模組化和容易維護。透過將相關的函式和資料放在一個 Module 中,可以提高程式碼的可讀性和重用性。同時,由於 Module 的成員可以直接存取,不需要建立物件實體,也可以簡化程式碼的撰寫。
建立和使用 Module
在 VB.NET 中,使用 Module 關鍵字來宣告一個 Module。以下是宣告 Module 的基本語法:
Module 模組名稱
' 模組成員(變數、常數、函式等)
End Module
Module 可以包含以下成員:
- 變數:使用 Dim 關鍵字宣告,可以是任何資料型別。
- 常數:使用 Const 關鍵字宣告,值不能修改。
- 函式:使用 Function 或 Sub 關鍵字宣告,可以有參數和回傳值。
以下是一個簡單的 Module 範例:
Module MathUtils
' 宣告一個常數
Public Const PI As Double = 3.14159
' 宣告一個函式
Public Function AreaOfCircle(radius As Double) As Double
Return PI * radius * radius
End Function
End Module
在這個範例中,宣告了一個名為 MathUtils 的 Module,其中包含一個常數 PI 和一個函式 AreaOfCircle。這個函式用來計算圓的面積,接受一個 Double 型別的參數 radius,表示圓的半徑,並使用常數 PI 來計算面積。
要使用 Module 中的成員,只需要直接使用成員的名稱即可,不需要建立 Module 的實體。例如,要使用上面的 AreaOfCircle 函式,可以這樣寫:
使用的控制項:
- Label1:用來顯示計算結果
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim radius As Double = 5
Dim area As Double = MathUtils.AreaOfCircle(radius)
Label1.Text = $"半徑為 {radius} 的圓面積是 {area:F4}"
End Sub
End Class
在這個範例中,直接使用 MathUtils.AreaOfCircle 來呼叫 Module 中的函式,傳入半徑值,並將結果存儲在變數 area 中。然後將結果格式化為四位小數,顯示在 Label1 控制項中。
以下是一個更完整的範例,示範如何在 Windows Forms 應用程式中使用 Module:
使用的控制項:
- TextBox1:用來輸入要反轉的字串
- Button1:用來觸發反轉字串的操作
- Label1:用來顯示反轉後的字串
Module StringUtils
' 宣告一個函式,用來反轉字串
Public Function ReverseString(str As String) As String
Return New String(str.Reverse().ToArray())
End Function
End Module
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim input As String = TextBox1.Text
Dim reversed As String = StringUtils.ReverseString(input)
Label1.Text = $"反轉後的字串:{reversed}"
End Sub
End Class
在這個範例中,宣告了一個名為 StringUtils 的 Module,其中包含一個函式 ReverseString,用來反轉字串。在表單的按鈕點擊事件中,從 TextBox1 中獲取使用者輸入的字串,然後使用 StringUtils.ReverseString 函式將其反轉,最後將結果顯示在 Label1 中。
從這個範例可以看出,使用 Module 可以將一些常用的功能封裝起來,方便在不同的地方重複使用。這樣可以提高程式碼的可讀性和可維護性,同時也可以避免重複編寫相同的程式碼。
Module 的存取範圍
Module 中的成員預設是 Friend 存取範圍,這意味著它們只能在同一個組件(專案)中存取。如果要讓 Module 中的成員能夠在其他組件中存取,需要使用 Public 關鍵字修飾這些成員。
Public Module MathUtils
Public Const PI As Double = 3.14159
Public Function AreaOfCircle(radius As Double) As Double
Return PI * radius * radius
End Function
End Module
在這個範例中,使用 Public 關鍵字修飾了 Module 和它的成員,這樣它們就可以在其他組件中被存取了。
需要注意的是,即使 Module 本身被宣告為 Public,但如果它的成員沒有顯式地使用 Public 修飾,那麼這些成員仍然是 Friend 存取範圍。
除了 Public 和 Friend 之外,Module 中的成員還可以使用 Private 關鍵字修飾,表示它們只能在 Module 內部存取。這可以用來隱藏 Module 的內部實現細節,提供更好的封裝性。
Public Module StringUtils
Private Function Capitalize(str As String) As String
If String.IsNullOrEmpty(str) Then Return str
Return Char.ToUpper(str(0)) & str.Substring(1)
End Function
Public Function CapitalizeWords(str As String) As String
Return String.Join(" ", str.Split(" "c).Select(Function(word) Capitalize(word)))
End Function
End Module
在這個範例中,Capitalize 函式被宣告為 Private,因為它只是 CapitalizeWords 函式的一個內部實現細節,不需要在 Module 外部被存取。而 CapitalizeWords 函式被宣告為 Public,因為希望它能夠在其他地方被使用。
| 存取修飾詞 | 說明 |
|---|---|
| Public | 成員可以在任何地方存取,包括其他組件 |
| Friend (預設) | 成員只能在同一個組件內存取 |
| Private | 成員只能在 Module 內部存取 |
透過適當地使用這些存取修飾詞,可以控制 Module 中成員的可見性,提高程式碼的安全性和可維護性。一般來說,只有那些需要在外部使用的成員才應該被宣告為 Public,而內部的實現細節應該被隱藏起來,宣告為 Private。
Module 的繼承與實作
Module 與類別不同,它不支援繼承Module 就像一個獨立的工具箱,不能把一個工具箱放到另一個工具箱裡面。同樣地,一個 Module 不能繼承另一個 Module,也不能被其他類別繼承。。這意味著一個 Module 不能繼承另一個 Module,也不能被其他類別繼承。
儘管 Module 不支援繼承,但它可以實作介面Module 雖然不能繼承,但可以實作介面,就像一個工具箱可以根據不同的需求提供不同的功能一樣。實作介面可以讓 Module 提供某些約定的功能,而不需要繼承的關係。。透過實作介面,可以定義一組方法,然後在 Module 中提供這些方法的具體實現。這樣可以提高 Module 的抽象程度和可替換性。
以下是一個示範 Module 實作介面的範例:
Public Interface ILogger
Sub Log(message As String)
End Interface
Public Module ConsoleLogger
Implements ILogger
Public Sub Log(message As String) Implements ILogger.Log
Console.WriteLine($"[{DateTime.Now}] {message}")
End Sub
End Module
Public Module FileLogger
Implements ILogger
Private Const LogFilePath As String = "app.log"
Public Sub Log(message As String) Implements ILogger.Log
File.AppendAllText(LogFilePath, $"[{DateTime.Now}] {message}{Environment.NewLine}")
End Sub
End Module
在這個範例中,定義了一個 ILogger 介面,其中包含一個 Log 方法。然後宣告了兩個 Module:ConsoleLogger 和 FileLogger,它們都實作了 ILogger 介面,但提供了不同的實作方式。ConsoleLogger 會將日誌訊息輸出到控制台,而 FileLogger 則會將日誌訊息寫入到檔案中。
這樣,就可以在程式中使用 ILogger 介面來引用這些 Module,而不需要關心它們的具體實作:
Public Sub Main()
Dim loggers As ILogger() = {ConsoleLogger, FileLogger}
For Each logger As ILogger In loggers
logger.Log("程式開始執行")
Next
' 執行程式邏輯...
For Each logger As ILogger In loggers
logger.Log("程式執行完畢")
Next
End Sub
在這個範例中,建立了一個 ILogger 陣列,其中包含了 ConsoleLogger 和 FileLogger 兩個 Module。然後使用 For Each 迴圈來遍歷這個陣列,對每個 logger 呼叫 Log 方法。這樣,日誌訊息就會同時輸出到控制台和檔案中。
透過這種方式,可以將 Module 的實作細節隱藏在介面後面,提高程式碼的抽象程度和可維護性。同時,也可以方便地擴充或替換 Module 的實作,而不需要修改使用它們的程式碼。
Module 的限制與問題
儘管 Module 有很多優點,但它也有一些限制和潛在的問題需要注意:
- 命名空間污染Module 中的成員都在全域命名空間中,如果命名不當,可能會與其他程式碼產生命名衝突,造成難以診斷的錯誤。:由於 Module 的成員都是共享的,它們會直接存在於全域命名空間中。如果 Module 中的成員命名不當,可能會與其他程式碼產生命名衝突,導致難以診斷的錯誤。
- 缺乏封裝性Module 的所有成員默認都是 Public 的,這意味著它們可以在任何地方被訪問和修改,這可能會破壞程式的封裝性和安全性。:Module 的所有成員預設都是 Public 的,除非顯式地使用 Private 修飾。這意味著 Module 中的成員可以在任何地方被訪問和修改,可能會破壞程式的封裝性和安全性。
- 難以單元測試由於 Module 的成員都是靜態的,無法透過建立 Module 的實例來進行單元測試,這可能會增加測試的難度。:由於 Module 的成員都是靜態的,無法透過建立 Module 的實體來進行單元測試。這可能會增加測試的難度,因為無法使用依賴注入等技術來提供 Module 的模擬實現。
- 不支援多型Module 不能被繼承,也不能實現多型。這意味著無法透過建立 Module 的子類別來擴充或修改其行為,這在某些情況下可能會限制程式的彈性和可擴充性。:與類別不同,Module 不支援繼承和多型。這意味著無法透過建立 Module 的子類別來擴充或修改其行為,在某些情況下可能會限制程式的彈性和可擴充性。
為了避免這些問題,在使用 Module 時建議遵循以下一些原則:
- 為 Module 和其成員選擇有意義且不容易衝突的名稱。
- 只將真正需要共享的成員放在 Module 中,其他成員應該放在類別中。
- 使用 Private 修飾符來隱藏 Module 的內部實現細節。
- 盡量避免在 Module 中放置太多的邏輯,以保持程式的模組化和可測試性。
- 如果需要擴充或修改 Module 的行為,可以考慮使用擴充方法或介面來實現。
在適當的場景下,Module 可以簡化程式的結構和程式碼的重用。但如果使用不當,也可能會帶來一些難以維護和測試的問題。因此,在使用 Module 時,應該權衡其優缺點,並遵循上述原則來規避潛在的風險。
Module 應用範例
Module 在實際應用中有很多用途,以下是一些常見的應用範例:
1. 公用程式函式
Module 可以用來存放一些常用的公用程式函式,例如字串處理、日期轉換、檔案操作等。這樣可以避免在不同的類別中重複編寫這些函式,提高程式碼的重用性。
Public Module StringUtils
Public Function IsNullOrWhiteSpace(str As String) As Boolean
Return String.IsNullOrWhiteSpace(str)
End Function
Public Function ToTitleCase(str As String) As String
Return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str)
End Function
Public Function RemoveExtraSpaces(str As String) As String
Return Regex.Replace(str, "\s+", " ").Trim()
End Function
End Module
在這個範例中,宣告了一個 StringUtils Module,其中包含了三個常用的字串處理函式:IsNullOrWhiteSpace 用來判斷字串是否為 Null 或空白、ToTitleCase 用來將字串轉換為標題格式、RemoveExtraSpaces 用來移除字串中的多餘空白。
這樣,在需要進行字串處理的地方,就可以直接使用這些函式,而不需要重複編寫它們:
使用的控制項:
- Label1:用來顯示輸出結果
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim name As String = " john doe "
If StringUtils.IsNullOrWhiteSpace(name) Then
Label1.Text = "名字不能為空"
Else
name = StringUtils.RemoveExtraSpaces(name)
name = StringUtils.ToTitleCase(name)
Label1.Text = $"您好,{name}" ' 輸出 "您好,John Doe"End If
End Sub
End Class
2. 設定檔存取
Module 也可以用來存放設定檔的存取函式,提供一個集中的地方來讀寫設定資料。這樣可以避免在不同的類別中重複編寫存取設定檔的程式碼,提高程式碼的可維護性。
Public Module ConfigManager
Private ReadOnly Property ConfigFilePath As String = "app.config"
Public Function GetSetting(key As String) As String
Dim value As String = ConfigurationManager.AppSettings(key)
If value Is Nothing Then
Throw New KeyNotFoundException($"找不到設定項:{key}")
End If
Return value
End Function
Public Sub SetSetting(key As String, value As String)
Dim config As Configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
config.AppSettings.Settings(key).Value = value
config.Save(ConfigurationSaveMode.Modified)
ConfigurationManager.RefreshSection("appSettings")
End Sub
End Module
在這個範例中,宣告了一個 ConfigManager Module,其中包含了兩個函式:GetSetting 用來從設定檔中讀取指定的設定項,SetSetting 用來將指定的設定項寫入到設定檔中。
這樣,在需要讀寫設定資料的地方,就可以直接使用這些函式,而不需要關心設定檔的具體位置和格式:
使用的控制項:
- Label1:用來顯示讀取的設定值
- Label2:用來顯示寫入的設定值
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
Dim apiUrl As String = ConfigManager.GetSetting("ApiUrl")
Label1.Text = $"API 地址:{apiUrl}"
ConfigManager.SetSetting("Timeout", "30")
Label2.Text = "已設定超時時間為 30 秒"
Catch ex As Exception
MessageBox.Show($"設定檔存取出錯:{ex.Message}")
End Try
End Sub
End Class
3. 日誌記錄
Module 還可以用來實作日誌記錄的功能,提供一個統一的地方來記錄程式的執行狀態和錯誤訊息。這樣可以方便地追蹤和診斷程式的問題,提高程式的可維護性。
Public Module Logger
Private ReadOnly Property LogFilePath As String = "app.log"
Public Sub Log(message As String)
File.AppendAllText(LogFilePath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}{Environment.NewLine}")
End Sub
Public Sub LogError(ex As Exception)
Log($"發生錯誤:{ex.Message}")
Log($"錯誤堆疊:{ex.StackTrace}")
End Sub
End Module
在這個範例中,宣告了一個 Logger Module,其中包含了兩個函式:Log 用來記錄一般的日誌訊息,LogError 用來記錄錯誤訊息和堆疊。
這樣,在需要記錄日誌的地方,就可以直接使用這些函式,而不需要關心日誌檔案的具體位置和格式:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Logger.Log("程式開始執行")
Try
' 執行程式邏輯...
' ...
Catch ex As Exception
Logger.LogError(ex)
MessageBox.Show("程式執行時發生錯誤,請查看日誌檔案了解詳情。")
End Try
Logger.Log("程式執行完畢")
End Sub
End Class
在這個範例中,在表單載入時記錄了程式開始執行的訊息,然後在 Try-Catch 區塊中執行程式邏輯。如果發生了異常,就使用 LogError 方法記錄錯誤訊息和堆疊,並顯示一個訊息方塊提示使用者查看日誌檔案。最後,在程式執行完畢時記錄一條訊息。
這些只是 Module 的一些常見應用範例,實際上 Module 還可以用於很多其他的場景,例如狀態管理、快取存取、加密解密等。透過將這些通用的功能封裝在 Module 中,可以提高程式碼的重用性、可維護性和可讀性,讓開發人員能夠專注於業務邏輯的實現,而不需要重複編寫底層的程式碼。