2024年6月26日 星期三

18.VB.NET 筆記 基礎篇 - Module

VB.NET Module 筆記 (基礎篇)

VB.NET Module 筆記 (基礎篇)

Module 就像是程式設計中的共享工具箱Module 可以包含各種共享的變數、函式、常數等,就像一個裝著常用工具的箱子,需要的時候隨時可以拿出來用。,它提供了一種方便的方式來組織和共享程式碼。透過瞭解 Module 的特性和使用方法,可以讓程式設計更加簡潔、易讀、易於維護。

認識 Module

Module: Module 是一種特殊的類別,它可以包含變數、常數、函式等成員,但不能建立物件實體。Module 的成員可以在整個專案中直接存取,不需要透過物件實體。它通常用來放置一些共用的公用程式函式或共享資料。

Module 有以下幾個重要特性:

  1. 不可建立實體Module 就像是一個共享的工具箱,不能建立多個實體,但可以直接使用裡面的工具。同樣地,Module 不能建立物件實體,但可以直接使用其中的成員。:Module 不能使用 New 關鍵字建立物件實體,它的成員都是共享的。
  2. 不能繼承Module 就像是一個獨立的工具箱,不能把一個工具箱放到另一個工具箱裡面。同樣地,Module 不能被其他類別繼承,也不能繼承其他類別。:Module 不能被其他類別繼承,也不能繼承其他類別。
  3. 共享成員Module 裡的工具可以被大家共用,不需要每個人都買一套。同樣地,Module 的成員可以在整個專案中共享,不需要透過物件實體存取。:Module 的成員可以在整個專案中直接存取,不需要透過物件實體。
  4. 預設為 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:ConsoleLoggerFileLogger,它們都實作了 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 陣列,其中包含了 ConsoleLoggerFileLogger 兩個 Module。然後使用 For Each 迴圈來遍歷這個陣列,對每個 logger 呼叫 Log 方法。這樣,日誌訊息就會同時輸出到控制台和檔案中。

透過這種方式,可以將 Module 的實作細節隱藏在介面後面,提高程式碼的抽象程度和可維護性。同時,也可以方便地擴充或替換 Module 的實作,而不需要修改使用它們的程式碼。

Module 的限制與問題

儘管 Module 有很多優點,但它也有一些限制和潛在的問題需要注意:

  1. 命名空間污染Module 中的成員都在全域命名空間中,如果命名不當,可能會與其他程式碼產生命名衝突,造成難以診斷的錯誤。:由於 Module 的成員都是共享的,它們會直接存在於全域命名空間中。如果 Module 中的成員命名不當,可能會與其他程式碼產生命名衝突,導致難以診斷的錯誤。
  2. 缺乏封裝性Module 的所有成員默認都是 Public 的,這意味著它們可以在任何地方被訪問和修改,這可能會破壞程式的封裝性和安全性。:Module 的所有成員預設都是 Public 的,除非顯式地使用 Private 修飾。這意味著 Module 中的成員可以在任何地方被訪問和修改,可能會破壞程式的封裝性和安全性。
  3. 難以單元測試由於 Module 的成員都是靜態的,無法透過建立 Module 的實例來進行單元測試,這可能會增加測試的難度。:由於 Module 的成員都是靜態的,無法透過建立 Module 的實體來進行單元測試。這可能會增加測試的難度,因為無法使用依賴注入等技術來提供 Module 的模擬實現。
  4. 不支援多型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 中,可以提高程式碼的重用性、可維護性和可讀性,讓開發人員能夠專注於業務邏輯的實現,而不需要重複編寫底層的程式碼。