(7)從護士到C#開發者--物件導向程式設計基礎

(7)從護士到C#開發者--物件導向程式設計基礎

作為一名從護理行業轉行的程式設計師,我將分享如何透過醫護工作經驗來理解物件導向程式設計的概念。本文將介紹類別、物件、屬性、方法等物件導向的核心概念,並結合醫療保健領域的實例來加深理解。

最後更新 2025/3/18 下午9:15
勇敢的天使
預計閱讀 17 分鐘
分類
分享 課程 .NET
專題
從護士到C#開發者
標籤
.NET C# 轉行開發 程式設計 物件導向

引言

作為一名從護理行業轉行到程式設計領域的新手,我發現物件導向程式設計(OOP)概念實際上與醫療實務有許多相似之處。在這篇文章中,我將分享我如何透過醫護工作經驗來理解物件導向程式設計的基本概念,希望能幫助其他正在學習程式設計的醫護人員。

程序導向 vs 物件導向

程序導向思維

程序導向程式設計關注的是完成事情的過程和步驟,強調的是動作和流程。這就像我們在醫院執行的標準操作流程(SOP)。

舉個例子,測量病人血糖的程序導向思維:

// 程序導向思維:不同護士執行相同的血糖測量流程
void NurseLiMeasureBloodSugar()  // 李護士(經驗少)
{
    PrepareEquipment();     // 準備設備
    CallForHelp();          // 尋求幫助
    ExplainToPatient();     // 向患者解釋
    DisinfectFinger();      // 消毒手指
    PrickFinger();          // 採血
    ApplyBloodToStrip();    // 血液滴到試紙
    ReadResult();           // 讀取結果
    RecordData();           // 記錄資料
    DisposeMaterials();     // 處理醫療廢棄物
}

void NurseZhangMeasureBloodSugar()  // 張護士(經驗豐富)
{
    PrepareEquipment();     // 準備設備
    ExplainToPatient();     // 向患者解釋
    DisinfectFinger();      // 消毒手指
    PrickFingerExpertly();  // 熟練採血
    ApplyBloodToStrip();    // 血液滴到試紙
    ReadResultAccurately(); // 精確讀取結果
    RecordDataInDetail();   // 詳細記錄資料
    DisposeMaterials();     // 處理醫療廢棄物
}

可以看出,程序導向的方法需要我們為不同的執行者編寫不同的程式碼,每位護士都需要自己的一套完整流程,導致程式碼重複且難以維護。

物件導向思維

物件導向程式設計則是找個物件幫你做事。針對同樣的血糖測量任務,我們把注意力從「誰怎麼做」轉移到「用什麼設備做」,將操作封裝到對應的物件中。

/// <summary>
/// 血糖儀類別 - 負責測量和記錄血糖值的醫療設備
/// </summary>
public class GlucoseMeter
{
    // 屬性 - 表示血糖儀的狀態
    /// <summary>
    /// 表示血糖儀是否已校正
    /// </summary>
    public bool IsCalibrated { get; private set; }

    /// <summary>
    /// 表示血糖儀目前電量
    /// </summary>
    public double BatteryLevel { get; private set; }

    // 方法 - 表示血糖儀可執行的操作
    /// <summary>
    /// 校正血糖儀,確保測量準確
    /// </summary>
    public void Calibrate()
    {
        IsCalibrated = true;
        Console.WriteLine("血糖儀已校正");
    }

    /// <summary>
    /// 執行血糖測量
    /// </summary>
    /// <returns>測量的血糖值,若設備未校法則回傳-1</returns>
    public double MeasureBloodSugar()
    {
        if(!IsCalibrated)
        {
            Console.WriteLine("錯誤:血糖儀未校正");
            return -1; // 錯誤值
        }

        Console.WriteLine("測量血糖中...");
        // 執行測量邏輯
        return 5.6; // 回傳血糖值(範例)
    }
}

/// <summary>
/// 護士類別 - 代表執行醫療操作的護理人員
/// </summary>
public class Nurse
{
    /// <summary>
    /// 護士姓名
    /// </summary>
    public string Name { get; private set; }

    /// <summary>
    /// 工作經驗年限
    /// </summary>
    public int ExperienceYears { get; private set; }

    /// <summary>
    /// 建立一個新的護士物件
    /// </summary>
    /// <param name="name">護士姓名</param>
    /// <param name="experienceYears">工作經驗年限</param>
    public Nurse(string name, int experienceYears)
    {
        Name = name;
        ExperienceYears = experienceYears;
    }

    /// <summary>
    /// 為患者測量血糖的綜合操作
    /// </summary>
    /// <param name="patient">需要測量的患者</param>
    /// <param name="meter">使用的血糖儀</param>
    public void MeasurePatientBloodSugar(Patient patient, GlucoseMeter meter)
    {
        Console.WriteLine($"{Name}護士準備為{patient.Name}測量血糖");

        // 準備設備
        Console.WriteLine("準備設備");

        // 校正設備(由血糖儀完成)
        meter.Calibrate();

        // 向患者解釋
        Console.WriteLine($"向{patient.Name}解釋測量過程");

        // 消毒和採血(護士的專業操作)
        Console.WriteLine("消毒手指並採血");

        // 測量血糖(由血糖儀完成)
        double bloodSugar = meter.MeasureBloodSugar();

        // 記錄結果
        if(bloodSugar > 0)
        {
            patient.RecordBloodSugar(bloodSugar);
            Console.WriteLine($"血糖值: {bloodSugar}mmol/L,已記錄到患者檔案");
        }

        // 處理醫療廢棄物
        Console.WriteLine("處理醫療廢棄物");
    }
}

/// <summary>
/// 患者類別 - 代表醫院中的病人
/// </summary>
public class Patient
{
    // 欄位 - 私有資料,不直接對外暴露
    /// <summary>
    /// 患者姓名
    /// </summary>
    private string _name;

    /// <summary>
    /// 患者年齡
    /// </summary>
    private int _age;

    /// <summary>
    /// 患者的歷史血糖記錄
    /// </summary>
    private List<double> _bloodSugarReadings;

    /// <summary>
    /// 建立一個新的患者物件
    /// </summary>
    /// <param name="name">患者姓名</param>
    /// <param name="age">患者年齡</param>
    public Patient(string name, int age)
    {
        _name = name;
        _age = age;
        _bloodSugarReadings = new List<double>(); // 初始化空的血糖記錄清單
    }

    // 屬性 - 對外提供的存取器
    /// <summary>
    /// 取得患者姓名
    /// </summary>
    public string Name
    {
        get { return _name; }
        private set { _name = value; } // 只允許在類別內部修改
    }

    /// <summary>
    /// 取得或設定患者年齡(包含驗證)
    /// </summary>
    public int Age
    {
        get { return _age; }
        private set { _age = value > 0 ? value : 0; } // 確保年齡為正數
    }

    // 方法 - 物件可執行的操作
    /// <summary>
    /// 記錄新的血糖測量值
    /// </summary>
    /// <param name="value">血糖值</param>
    public void RecordBloodSugar(double value)
    {
        if(value > 0)
        {
            _bloodSugarReadings.Add(value);
        }
    }

    /// <summary>
    /// 取得最近一次血糖記錄
    /// </summary>
    /// <returns>最近的血糖值,如無記錄則回傳-1</returns>
    public double GetLatestBloodSugar()
    {
        if(_bloodSugarReadings.Count > 0)
            return _bloodSugarReadings[_bloodSugarReadings.Count - 1];
        else
            return -1; // 表示沒有記錄
    }

    /// <summary>
    /// 取得患者的所有血糖記錄歷史
    /// </summary>
    /// <returns>血糖值清單的副本</returns>
    public List<double> GetBloodSugarHistory()
    {
        return new List<double>(_bloodSugarReadings); // 回傳副本以保護原始資料
    }
}

// 使用範例 - 示範物件之間的合作
/// <summary>
/// 血糖測量的綜合範例
/// </summary>
void BloodSugarMeasurementExample()
{
    // 建立各種物件
    Patient patient = new Patient("王小明", 65);
    GlucoseMeter meter = new GlucoseMeter();

    // 不同經驗的護士使用相同的方法和設備
    Nurse nurseLi = new Nurse("李護士", 1);  // 經驗少
    Nurse nurseZhang = new Nurse("張護士", 10);  // 經驗豐富

    // 兩位護士執行相同的操作,呼叫相同的方法
    nurseLi.MeasurePatientBloodSugar(patient, meter);
    nurseZhang.MeasurePatientBloodSugar(patient, meter);

    // 檢查患者的血糖歷史
    List<double> history = patient.GetBloodSugarHistory();
    Console.WriteLine($"患者{patient.Name}的血糖記錄次數: {history.Count}");
}

透過物件導向的方法,我們:

  1. 建立了表示不同角色和設備的物件(患者、護士、血糖儀)
  2. 每個物件負責自己的功能(血糖儀負責測量,護士負責操作流程)
  3. 無論是哪位護士,都使用相同的方法,減少了程式碼重複
  4. 將責任分配給了合適的物件,使程式碼更加模組化且易於維護

這就像現實中的醫療工作一樣:不同的護士使用相同的醫療設備和標準流程為患者服務,而不是每位護士都發明自己的流程。

理解類別與物件

在醫院工作時,我們有「護理記錄單」這個範本,根據它可以為每個病人建立具體的記錄。在程式設計中:

  • 類別:就像護理記錄單範本,定義了物件應有的特徵(屬性)和功能(方法)
  • 物件:就像根據範本填寫的具體病人記錄

類別的概念

類別就像是一張圖紙或模子,就像我之前做護士時使用的護理評估表格。這個表格定義了需要記錄的內容(屬性)和操作流程(方法)。

在 C#中,類別的基本語法是:

[public] class 類別名稱
{
    // 欄位
    private string _name;

    // 屬性
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    // 方法
    public void DoSomething()
    {
        // 方法實作
    }
}

類別和物件的關係

如果把類別比作病歷範本,那麼物件就是根據這個範本建立的具體病人記錄。類別本身不佔用記憶體(除靜態成員外),而物件是要佔用記憶體的。

醫護實例:

// Patient類別(病人類別)
public class Patient
{
    // 欄位
    private string _name;
    private int _age;
    private string _diagnosis;

    // 屬性
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public int Age
    {
        get { return _age; }
        set { _age = value > 0 ? value : 0; }  // 驗證年齡必須為正數
    }

    // 方法
    public void TakeMedicine()
    {
        Console.WriteLine($"{Name}正在服藥...");
    }
}

// 建立Patient物件
Patient patient1 = new Patient();
patient1.Name = "張三";
patient1.Age = 45;
patient1.TakeMedicine();  // 輸出:張三正在服藥...

理解屬性和欄位

欄位與屬性的區別

在護理工作中,我們有很多資料需要記錄,但有些資料需要經過驗證或特殊處理。

  • 欄位:相當於病歷上的原始資料,通常應該被保護,不允許外部直接修改
  • 屬性:相當於對這些資料的管控機制,可以加入驗證邏輯
public class Patient
{
    // 欄位(私有)
    private double _temperature;

    // 屬性(公開)
    public double Temperature
    {
        get { return _temperature; }
        set
        {
            // 加入驗證邏輯
            if(value >= 35 && value <= 42)
                _temperature = value;
            else
                throw new ArgumentException("體溫數值不合理!");
        }
    }
}

屬性的本質是兩個方法:get()set()。屬性可以是:

  • 可讀可寫屬性:有 get 和 set
  • 唯讀屬性:只有 get
  • 唯寫屬性:只有 set

存取修飾詞

在醫院資訊系統中,不同層級的人員能夠存取的資訊是不同的。這與 C#中的存取修飾詞概念類似:

  • public:公開的,就像病人的基本資訊,所有醫護人員都可以查看
  • private:私有的,只能在目前類別內部存取,就像某些敏感檢查結果,只有主治醫生可以查看
  • protected:受保護的,可以被衍生類別存取,就像某些治療方案,只有醫療團隊成員可以查看

物件的初始化

當新病人入院時,我們需要填寫一系列表格來記錄其基本資訊。在程式設計中,我們透過建構函式來完成物件的初始化工作。

建構函式

建構函式是一個特殊的方法,它在物件建立時自動執行,用於初始化物件。

public class Patient
{
    // 欄位
    private string _name;
    private int _age;

    // 建構函式
    public Patient(string name, int age)
    {
        _name = name;
        _age = age;
        Console.WriteLine($"建立了一個新病人: {name}, {age}歲");
    }

    // 屬性
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public int Age
    {
        get { return _age; }
        set { _age = value; }
    }
}

// 使用建構函式建立物件
Patient newPatient = new Patient("李四", 50);

建構函式的特點:

  1. 沒有回傳值,連 void 都不能寫
  2. 名稱必須與類別名稱相同
  3. 可以有多個多載版本
  4. 類別中預設有一個無參數建構函式,但如果自訂了建構函式,預設的無參數建構函式就會消失

new 關鍵字的作用

當我們使用new建立物件時,它做了三件事:

  1. 在記憶體中開闢空間
  2. 在這個空間中建立物件
  3. 呼叫物件的建構函式進行初始化

就像醫院為新病人分配床位、準備病歷,然後錄入基本資訊。

靜態與非靜態

在醫院中,有些資訊是每個病人都不同的(如姓名、年齡),有些資訊則是所有病人共享的(如醫院名稱、科室醫生)。

靜態成員與執行個體成員

  • 執行個體成員:每個物件獨有的屬性或方法,就像每個病人都有自己的體溫、血壓等資料
  • 靜態成員:屬於類別本身而非物件的屬性或方法,所有物件共享,就像醫院的名稱、治療規範等
/// <summary>
/// 醫院類別(靜態) - 包含所有科室共用的醫院級資訊
/// </summary>
public static class Hospital
{
    // 靜態屬性 - 所有科室共用

    /// <summary>
    /// 醫院名稱 - 所有科室共用
    /// </summary>
    public static string Name { get; } = "和平醫院";

    /// <summary>
    /// 總床位數 - 由所有科室共用
    /// </summary>
    public static int TotalBeds { get; private set; } = 500;

    /// <summary>
    /// 醫院地址 - 所有科室共用
    /// </summary>
    public static string Address { get; } = "和平路123號";

    /// <summary>
    /// 急診電話 - 所有科室共用
    /// </summary>
    public static string EmergencyNumber { get; } = "120";

    // 靜態方法 - 醫院級別的操作
    /// <summary>
    /// 患者入院流程 - 分配到特定科室
    /// </summary>
    /// <param name="patient">入院患者</param>
    /// <param name="department">目標科室</param>
    public static void AdmitPatient(Patient patient, Department department)
    {
        if(TotalBeds > 0)
        {
            department.AssignBed(patient);
            TotalBeds--;
            Console.WriteLine($"{patient.Name}已入院到{department.Name}科");
        }
        else
        {
            Console.WriteLine("醫院床位已滿,無法入院");
        }
    }

    /// <summary>
    /// 患者出院流程 - 從特定科室出院
    /// </summary>
    /// <param name="patient">出院患者</param>
    /// <param name="department">來源科室</param>
    public static void DischargePatient(Patient patient, Department department)
    {
        department.ReleaseBed(patient);
        TotalBeds++;
        Console.WriteLine($"{patient.Name}已從{department.Name}科出院");
    }

}

/// <summary>
/// 科室類別 - 表示醫院中的具體科室,每個科室有自己的特性
/// </summary>
public class Department
{
    // 欄位 - 科室特有資料
    /// <summary>
    /// 科室名稱
    /// </summary>
    private string _name;

    /// <summary>
    /// 科室總床位數
    /// </summary>
    private int _totalBeds;

    /// <summary>
    /// 科室可用床位數
    /// </summary>
    private int _availableBeds;

    /// <summary>
    /// 科室醫護人員ID清單
    /// </summary>
    private List<string> _staffIds;

    /// <summary>
    /// 科室目前病人清單
    /// </summary>
    private List<Patient> _patients;

    /// <summary>
    /// 建立一個新的科室
    /// </summary>
    /// <param name="name">科室名稱</param>
    /// <param name="totalBeds">總床位數</param>
    public Department(string name, int totalBeds)
    {
        _name = name;
        _totalBeds = totalBeds;
        _availableBeds = totalBeds;
        _staffIds = new List<string>();
        _patients = new List<Patient>();
    }

    // 屬性 - 科室對外暴露的資訊
    /// <summary>
    /// 取得科室名稱
    /// </summary>
    public string Name { get { return _name; } }

    /// <summary>
    /// 取得科室可用床位數
    /// </summary>
    public int AvailableBeds { get { return _availableBeds; } }

    // 方法 - 科室可執行的操作
    
    /// <summary>
    /// 為患者分配床位
    /// </summary>
    /// <param name="patient">需要床位的患者</param>
    public void AssignBed(Patient patient)
    {
        if(_availableBeds > 0)
        {
            _patients.Add(patient);
            _availableBeds--;
            Console.WriteLine($"{patient.Name}已分配到{_name}科床位");
        }
        else
        {
            Console.WriteLine($"{_name}科床位已滿");
        }
    }

    /// <summary>
    /// 患者出院釋放床位
    /// </summary>
    /// <param name="patient">出院患者</param>
    public void ReleaseBed(Patient patient)
    {
        if(_patients.Contains(patient))
        {
            _patients.Remove(patient);
            _availableBeds++;
            Console.WriteLine($"{patient.Name}已釋放{_name}科床位");
        }
    }

    /// <summary>
    /// 新增醫護人員到本科室
    /// </summary>
    /// <param name="staffId">醫護人員ID</param>
    public void AddStaff(string staffId)
    {
        _staffIds.Add(staffId);
    }

    /// <summary>
    /// 顯示科室目前狀態資訊
    /// </summary>
    public void DisplayDepartmentStatus()
    {
        // 這裡同時存取靜態成員(醫院名)和執行個體成員(科室資訊)
        Console.WriteLine($"{Hospital.Name} - {_name}科");
        Console.WriteLine($"總床位:{_totalBeds},可用床位:{_availableBeds}");
        Console.WriteLine($"目前患者數:{_patients.Count}");
    }

}

this 關鍵字

this關鍵字表示目前類別的執行個體,在醫護工作中,可以理解為「目前正在處理的這個病人」。

this 的用途:

  1. 區分區域變數和成員變數
  2. 在建構函式中呼叫其他建構函式
public class Patient
{
    private string name;
    private int age;

    // 使用this區分同名變數
    public Patient(string name, int age)
    {
        this.name = name;  // this.name是欄位,name是參數
        this.age = age;
    }

    // 使用this呼叫其他建構函式
    public Patient(string name) : this(name, 0)
    {
        // 這個建構函式會先呼叫上面的建構函式
    }
}

用藥記錄範例

下面是一個用藥記錄類別的完整範例,示範了物件導向程式設計在醫療場景中的應用:

/// <summary>
/// 用藥記錄類別 - 記錄患者用藥的詳細資訊
/// </summary>
public class MedicationRecord
{
    // 欄位 - 記錄的內部資料

    /// <summary>
    /// 藥品名稱
    /// </summary>
    private string _medicationName;

    /// <summary>
    /// 藥品劑量(數值部分)
    /// </summary>
    private double _dosage;
    
    /// <summary>
    /// 劑量單位(如mg, ml等)
    /// </summary>
    private string _unit;
    
    /// <summary>
    /// 給藥時間
    /// </summary>
    private DateTime _administrationTime;
    
    /// <summary>
    /// 執行給藥的護士ID
    /// </summary>
    private string _nurseId;
    
    /// <summary>
    /// 接受給藥的患者ID
    /// </summary>
    private string _patientId;
    
    /// <summary>
    /// 給藥途徑(口服、靜脈注射等)
    /// </summary>
    private string _route;
    
    /// <summary>
    /// 建立新的用藥記錄
    /// </summary>
    /// <param name="patientId">患者ID</param>
    /// <param name="medicationName">藥品名稱</param>
    /// <param name="dosage">藥品劑量</param>
    /// <param name="unit">劑量單位</param>
    /// <param name="route">給藥途徑</param>
    /// <param name="nurseId">執行護士ID</param>
    public MedicationRecord(string patientId, string medicationName,
                           double dosage, string unit, string route, string nurseId)
    {
        _patientId = patientId;
        _medicationName = medicationName;
        _dosage = dosage;
        _unit = unit;
        _route = route;
        _nurseId = nurseId;
        _administrationTime = DateTime.Now; // 記錄目前時間為給藥時間
    }
    
    // 屬性 - 對外部提供的資料存取介面
    
    /// <summary>
    /// 取得藥品名稱(唯讀)
    /// </summary>
    public string MedicationName { get { return _medicationName; } }
    
    /// <summary>
    /// 取得劑量(唯讀)
    /// </summary>
    public double Dosage { get { return _dosage; } }
    
    /// <summary>
    /// 取得劑量單位(唯讀)
    /// </summary>
    public string Unit { get { return _unit; } }
    
    /// <summary>
    /// 取得給藥時間(唯讀)
    /// </summary>
    public DateTime AdministrationTime { get { return _administrationTime; } }
    
    // 方法 - 記錄相關的操作
    
    /// <summary>
    /// 取得完整的劑量資訊
    /// </summary>
    /// <returns>格式化的劑量和給藥途徑資訊</returns>
    public string GetFullDosageInfo()
    {
        return $"{_dosage} {_unit} {_medicationName} 透過{_route}給藥";
    }
    
    /// <summary>
    /// 取得完整的給藥記錄
    /// </summary>
    /// <returns>格式化的完整給藥記錄</returns>
    public string GetAdministrationRecord()
    {
        return $"患者ID: {_patientId}, 藥品: {_medicationName}, " +
               $"劑量: {_dosage}{_unit}, 途徑: {_route}, " +
               $"時間: {_administrationTime.ToString("yyyy-MM-dd HH:mm:ss")}, " +
               $"執行護士: {_nurseId}";
    }
    
    /// <summary>
    /// 檢查是否在指定時間閾值內給藥
    /// </summary>
    /// <param name="hoursThreshold">小時閾值</param>
    /// <returns>如果在閾值內回傳true,否則回傳false</returns>
    public bool IsAdministeredRecently(int hoursThreshold)
    {
        return (DateTime.Now - _administrationTime).TotalHours < hoursThreshold;
    }

}

// 使用範例
public class MedicationExample
{
    public static void Main()
    {
        // 建立用藥記錄
        MedicationRecord record = new MedicationRecord(
        "P001", "阿斯匹靈", 100, "mg", "口服", "N007");

        // 取得完整記錄
        string fullRecord = record.GetAdministrationRecord();
        Console.WriteLine(fullRecord);
    
        // 檢查是否最近給藥(6小時內)
        bool isRecent = record.IsAdministeredRecently(6);
        Console.WriteLine($"是否6小時內給藥: {isRecent}");
    }

}

總結

作為一名從護士轉行到程式設計的新手,我發現物件導向程式設計的概念與醫療實務有許多相似之處:

  • 類別就像是病歷表格範本
  • 物件就像是具體病人的病歷記錄
  • 屬性就像是病人的各項指標,有些需要資料驗證
  • 方法就像是對病人進行的各種醫療操作
  • 建構函式就像是病人入院登記
  • 靜態成員就像是醫院的共用資源和規範

透過這些類比,我更容易理解和記憶物件導向程式設計的概念。在下一篇文章中,我將繼續深入探討物件導向程式設計的進階概念,如繼承、多型和介面。

期待與大家分享更多從醫護到程式設計的學習心得!

繼續探索

延伸閱讀

更多文章
同分類 / 同標籤 2025/8/13

推薦一款高效能狀態機管理解決方案

在實際軟體開發中,尤其是工業軟體,每一款設備都有複雜的狀態以及狀態之間的切換的功能需求,在這種情況下,如何管理狀態以及狀態之間切換,和對應狀態下的功能控制,成為非常重要的一個問題。

繼續閱讀
同分類 / 同標籤 2025/2/25

.NET 10 Preview 1 發佈

今天 .NET 10 Preview 1 發佈了,我第一時間下載,升級了 Avalonia UI 專案和部落格網站,前者功能測試及 AOT 發佈正常,後者偵錯正常,Docker 暫時未成功

繼續閱讀