2024年6月10日 星期一

13.VB.NET 筆記 進階篇 - 建構式 (Constructor)

VB.NET 建構式(Constructor)筆記(進階篇)

VB.NET 建構式(Constructor) 筆記(進階篇)

Constructor 是物件建立時自動執行的初始化程序。在 VB.NET 中,建構式寫成 Sub New。它不只是用來填預設值,更重要的是讓物件一建立就符合必要規則,避免產生半完成、資料不完整或狀態不合理的物件。

建構式進階篇的重點,是理解不同初始化方式如何安排:何時使用必要參數、何時使用重載、何時用 Optional、何時讓建構式呼叫另一個建構式,以及繼承時為什麼要先呼叫 MyBase.New

先理解建構式的角色

建構式是物件建立時的入口

只要程式寫下 New 類別名稱(...),就會進入該類別對應的 Sub New。因此,建構式很適合放「物件一開始就必須成立」的規則。

  • 設定初始值:例如狀態、編號、預設數量。
  • 接收必要資料:例如名稱、代碼、容量、起始金額。
  • 檢查資料合理性:例如數量不可小於 0、名稱不可空白。
  • 串接父類別初始化:繼承時先完成基底類別需要的資料。

建構式適合處理的事情:

  1. 必要資料:缺少就不應該建立物件。
  2. 初始狀態:建立後馬上要能正常使用。
  3. 固定規則:不希望外部到處重複寫同樣初始化邏輯。

基本語法

VB.NET
Public Class 類別名稱
    Public Sub New(ByVal 參數名稱 As 資料型別)
        ' 建立物件時要執行的初始化流程
    End Sub
End Class

預設建構式與必要參數

場景一:建立取餐叫號卡

這個範例用取餐叫號卡示範兩種建立方式:沒有參數時建立「尚未發號」狀態;有參數時建立可顯示的取餐卡。

需要的主控項
  • ButtonCreateEmpty:建立尚未發號的叫號卡。
  • ButtonCreateTicket:建立正式叫號卡。
  • LabelTicket:顯示叫號卡內容。
範例程式碼
VB.NET / Windows Forms
Public Class PickupTicket
    Public Property TicketNo As String
    Public Property CounterName As String
    Public Property IsReady As Boolean

    Public Sub New()
        TicketNo = "尚未發號"
        CounterName = "未分配櫃台"
        IsReady = False
    End Sub

    Public Sub New(ByVal ticketNo As String, ByVal counterName As String)
        Me.TicketNo = ticketNo
        Me.CounterName = counterName
        Me.IsReady = True
    End Sub

    Public Function BuildText() As String
        Dim readyText As String = If(IsReady, "可取餐", "等待發號")
        Return TicketNo & "|" & CounterName & "|" & readyText
    End Function
End Class

Public Class Form1
    Private Sub ButtonCreateEmpty_Click(sender As Object, e As EventArgs) Handles ButtonCreateEmpty.Click
        Dim ticket As New PickupTicket()
        LabelTicket.Text = ticket.BuildText()
    End Sub

    Private Sub ButtonCreateTicket_Click(sender As Object, e As EventArgs) Handles ButtonCreateTicket.Click
        Dim ticket As New PickupTicket("B-18", "二號櫃台")
        LabelTicket.Text = ticket.BuildText()
    End Sub
End Class
畫面輸出結果(ButtonCreateTicket)
B-18|二號櫃台|可取餐
邏輯解析
  • Public Sub New() 是無參數建構式,用來建立預設狀態。
  • Public Sub New(ticketNo, counterName) 是帶參數建構式,用來建立完整叫號卡。
  • 不同建構式代表不同初始化路徑,但建立出來都是 PickupTicket 物件。

建構式重載與 Me.New

場景二:建立展場通行貼紙

建構式重載可以提供多種建立方式。若多個建構式有共用邏輯,可以用 Me.New(...) 呼叫同一個主要建構式,避免初始化規則分散。

需要的主控項
  • ButtonCreateVisitor:建立一般訪客貼紙。
  • ButtonCreateStaff:建立工作人員貼紙。
  • LabelBadge:顯示貼紙內容。
範例程式碼
VB.NET / Windows Forms
Public Class ExpoBadge
    Public Property DisplayName As String
    Public Property BadgeType As String
    Public Property CanEnterBackstage As Boolean

    Public Sub New(ByVal displayName As String)
        Me.New(displayName, "一般參觀", False)
    End Sub

    Public Sub New(ByVal displayName As String,
                   ByVal badgeType As String,
                   ByVal canEnterBackstage As Boolean)
        If displayName.Trim() = String.Empty Then
            Throw New ArgumentException("顯示名稱不可空白。")
        End If

        Me.DisplayName = displayName
        Me.BadgeType = badgeType
        Me.CanEnterBackstage = canEnterBackstage
    End Sub

    Public Function BuildText() As String
        Dim backstageText As String = If(CanEnterBackstage, "可進後台", "不可進後台")
        Return DisplayName & "|" & BadgeType & "|" & backstageText
    End Function
End Class

Public Class Form1
    Private Sub ButtonCreateVisitor_Click(sender As Object, e As EventArgs) Handles ButtonCreateVisitor.Click
        Dim badge As New ExpoBadge("來賓 042")
        LabelBadge.Text = badge.BuildText()
    End Sub

    Private Sub ButtonCreateStaff_Click(sender As Object, e As EventArgs) Handles ButtonCreateStaff.Click
        Dim badge As New ExpoBadge("場務小組", "工作人員", True)
        LabelBadge.Text = badge.BuildText()
    End Sub
End Class
畫面輸出結果(ButtonCreateStaff)
場務小組|工作人員|可進後台
邏輯解析
  • 單參數建構式用 Me.New(...) 呼叫三參數建構式。
  • 驗證規則集中在主要建構式中,不必重複寫兩次。
  • Me.New(...) 必須放在建構式的第一行。

Optional 參數與初始化驗證

場景三:建立閱讀活動預約資料

Optional 適合用在少量、單純的可選初始值。這個範例中,讀者姓名與場次代碼是必要資料,座位區與是否需要耳機則有預設值。

需要的主控項
  • TextBoxReaderName:輸入讀者姓名。
  • TextBoxSessionCode:輸入場次代碼。
  • ButtonReserve:建立預約。
  • LabelReservation:顯示預約結果。
範例程式碼
VB.NET / Windows Forms
Public Class ReadingReservation
    Public Property ReaderName As String
    Public Property SessionCode As String
    Public Property SeatArea As String
    Public Property NeedHeadset As Boolean

    Public Sub New(ByVal readerName As String,
                   ByVal sessionCode As String,
                   Optional ByVal seatArea As String = "自由座",
                   Optional ByVal needHeadset As Boolean = False)
        If readerName.Trim() = String.Empty Then
            Throw New ArgumentException("讀者姓名不可空白。")
        End If

        If sessionCode.Trim() = String.Empty Then
            Throw New ArgumentException("場次代碼不可空白。")
        End If

        Me.ReaderName = readerName
        Me.SessionCode = sessionCode
        Me.SeatArea = seatArea
        Me.NeedHeadset = needHeadset
    End Sub

    Public Function BuildText() As String
        Dim headsetText As String = If(NeedHeadset, "需要耳機", "不需耳機")
        Return ReaderName & "|" & SessionCode & "|" & SeatArea & "|" & headsetText
    End Function
End Class

Public Class Form1
    Private Sub ButtonReserve_Click(sender As Object, e As EventArgs) Handles ButtonReserve.Click
        Try
            Dim reservation As New ReadingReservation(
                TextBoxReaderName.Text.Trim(),
                TextBoxSessionCode.Text.Trim())

            LabelReservation.Text = reservation.BuildText()
        Catch ex As ArgumentException
            LabelReservation.Text = ex.Message
        End Try
    End Sub
End Class
畫面輸出結果(未指定 Optional 參數)
林同學|R-204|自由座|不需耳機
邏輯解析
  • readerNamesessionCode 是必要資料。
  • seatAreaneedHeadset 有預設值,可不傳入。
  • 必要資料驗證放在建構式中,能避免建立出不完整的預約物件。

Optional 不適合包辦所有情境。 若參數越來越多,或不同組合代表不同建立流程,建構式重載、Factory Method 或獨立設定物件通常會比一長串 Optional 參數清楚。

建構式與物件初始化器

場景四:建立報表匯出工作

建構式適合放必要資料,物件初始化器適合設定可選屬性。這樣可以讓物件建立時先具備最低可用狀態,再依需求補上額外設定。

需要的主控項
  • ButtonCreateJob:建立匯出工作。
  • LabelJob:顯示工作設定。
範例程式碼
VB.NET / Windows Forms
Public Class ExportJob
    Public ReadOnly Property JobName As String
    Public Property FileName As String
    Public Property IncludeTimestamp As Boolean
    Public Property IsCompressed As Boolean

    Public Sub New(ByVal jobName As String)
        If jobName.Trim() = String.Empty Then
            Throw New ArgumentException("工作名稱不可空白。")
        End If

        Me.JobName = jobName
        Me.FileName = "export.txt"
        Me.IncludeTimestamp = True
        Me.IsCompressed = False
    End Sub

    Public Function BuildText() As String
        Return JobName & "|" & FileName & "|壓縮:" & IsCompressed.ToString()
    End Function
End Class

Public Class Form1
    Private Sub ButtonCreateJob_Click(sender As Object, e As EventArgs) Handles ButtonCreateJob.Click
        Dim job As New ExportJob("每日摘要") With {
            .FileName = "daily-summary.csv",
            .IsCompressed = True
        }

        LabelJob.Text = job.BuildText()
    End Sub
End Class
畫面輸出結果(LabelJob.Text)
每日摘要|daily-summary.csv|壓縮:True
邏輯解析
  • JobName 是必要資料,因此放在建構式。
  • FileNameIsCompressed 是可選設定,因此用物件初始化器補上。
  • 建構式與初始化器不是互斥,而是分別處理必要與可選資料。

Private 建構式與建立方法

場景五:統一產生交班批次代碼

有些物件不希望外部隨便 New,因為建立時需要固定格式或統一規則。這時可以把建構式設為 Private,再提供公開的建立方法。

需要的主控項
  • ButtonCreateBatch:建立交班批次。
  • LabelBatch:顯示批次代碼。
範例程式碼
VB.NET / Windows Forms
Public Class ShiftBatch
    Public ReadOnly Property BatchCode As String
    Public ReadOnly Property CreatedAt As DateTime

    Private Sub New(ByVal batchCode As String, ByVal createdAt As DateTime)
        Me.BatchCode = batchCode
        Me.CreatedAt = createdAt
    End Sub

    Public Shared Function CreateMorningBatch(ByVal runningNo As Integer) As ShiftBatch
        If runningNo <= 0 Then
            Throw New ArgumentException("流水號必須大於 0。")
        End If

        Dim code As String = "M-" & Date.Today.ToString("MMdd") & "-" & runningNo.ToString("000")
        Return New ShiftBatch(code, DateTime.Now)
    End Function

    Public Function BuildText() As String
        Return BatchCode & "|建立時間:" & CreatedAt.ToString("HH:mm:ss")
    End Function
End Class

Public Class Form1
    Private Sub ButtonCreateBatch_Click(sender As Object, e As EventArgs) Handles ButtonCreateBatch.Click
        Dim batch As ShiftBatch = ShiftBatch.CreateMorningBatch(7)
        LabelBatch.Text = batch.BuildText()
    End Sub
End Class
畫面輸出結果(LabelBatch.Text)
M-0425-007|建立時間:09:30:12
邏輯解析
  • Private Sub New 讓外部不能直接任意建立物件。
  • CreateMorningBatch 集中建立規則,統一批次代碼格式。
  • Shared Function 可在不用先建立物件的情況下呼叫。

建構式與繼承

場景六:一般提醒與倒數提醒

繼承時,子類別建立之前必須先完成父類別的初始化。若父類別建構式需要參數,子類別通常要使用 MyBase.New(...) 明確傳入。

需要的主控項
  • ButtonShowNotice:建立提醒物件。
  • ListBoxNotice:顯示提醒內容。
範例程式碼
VB.NET / Windows Forms
Public Class NoticeBase
    Public ReadOnly Property Title As String

    Public Sub New(ByVal title As String)
        If title.Trim() = String.Empty Then
            Throw New ArgumentException("提醒標題不可空白。")
        End If

        Me.Title = title
    End Sub

    Public Overridable Function BuildText() As String
        Return "提醒:" & Title
    End Function
End Class

Public Class CountdownNotice
    Inherits NoticeBase

    Public ReadOnly Property MinutesLeft As Integer

    Public Sub New(ByVal title As String, ByVal minutesLeft As Integer)
        MyBase.New(title)

        If minutesLeft < 0 Then
            Throw New ArgumentException("剩餘分鐘不可小於 0。")
        End If

        Me.MinutesLeft = minutesLeft
    End Sub

    Public Overrides Function BuildText() As String
        Return "倒數 " & MinutesLeft.ToString() & " 分鐘|" & Title
    End Function
End Class

Public Class Form1
    Private Sub ButtonShowNotice_Click(sender As Object, e As EventArgs) Handles ButtonShowNotice.Click
        Dim notices As New List(Of NoticeBase) From {
            New NoticeBase("補上會議記錄"),
            New CountdownNotice("線上課程開始", 10)
        }

        ListBoxNotice.Items.Clear()

        For Each item As NoticeBase In notices
            ListBoxNotice.Items.Add(item.BuildText())
        Next
    End Sub
End Class
畫面輸出結果(ListBoxNotice)
提醒:補上會議記錄 倒數 10 分鐘|線上課程開始
邏輯解析
  • NoticeBase 的建構式負責檢查並設定標題。
  • CountdownNotice 使用 MyBase.New(title) 先完成父類別初始化。
  • 子類別再設定自己的 MinutesLeft
  • 初始化順序是先父類別,再子類別。

實務判斷與常見誤區

常見問題整理

  • 建構式做太多事:把大量檔案讀寫、資料庫連線或網路等待放進建構式,會讓物件建立過程難以控制。
  • 必要資料沒有放進建構式:先建立空物件再到處補資料,容易產生半完成狀態。
  • 重載過多:建構式版本太多時,外部反而難以判斷該使用哪一個。
  • Optional 過長:參數太多、規則太多時,應考慮改用設定物件或建立方法。
  • 繼承時忘記父類別初始化:父類別沒有無參數建構式時,子類別需明確呼叫 MyBase.New
做法 適合情境 注意事項
必要參數建構式 資料缺少就不應建立物件。 參數不要過多。
建構式重載 同一物件有少數幾種明確建立方式。 可用 Me.New 集中共同規則。
Optional 參數 只有少數可選設定。 過多會降低可讀性。
物件初始化器 必要資料已完成,可再補可選屬性。 不適合處理必要規則。
Private 建構式 建立規則需要集中控管。 通常搭配 Shared Function 建立物件。

重點整理

  1. 建構式在物件建立時自動執行,VB.NET 寫成 Sub New
  2. 建構式適合設定必要初始值與檢查建立規則。
  3. 無參數建構式適合建立預設狀態;帶參數建構式適合要求必要資料。
  4. 建構式重載可提供多種建立方式,但不宜過度增加版本。
  5. Me.New(...) 可讓一個建構式呼叫另一個建構式,集中共同初始化規則。
  6. Optional 適合少量可選初始值,不適合取代所有重載設計。
  7. 物件初始化器適合補可選屬性,建構式適合保證必要規則。
  8. 繼承時,子類別可透過 MyBase.New(...) 先完成父類別初始化。
  9. 建構式不適合放入太重、太慢或容易失敗的外部資源流程。