# 🏅 Day 7 - 介面(interface) ### 介面 (interface) 在 TypeScript 裡頭,介面(Interface)和 type 的作用類似,能夠用來定義物件的型別。 ### 基本範例 ```tsx= interface Student { studentId: number; name: string; } // 正確 let bob: Student = { studentId: 101, name: 'Bob' }; // 錯誤,只可指定已知的屬性,且 'Student' 中沒有 'gender'。 let Tom: Student = { studentId: 101, name: 'Tom', gender:'male' // 會報錯 }; ``` 這裡定義了一個 **`Student`** 介面,然後建立了一個符合這個介面的物件 **`bob`**。 TypeScript 要求物件必須跟介面的定義完全一致,物件多出或少了介面中的屬性都不行。 ### 可選屬性 如果有些屬性是選擇性的,可以在屬性名稱後面加上一個問號 `?`: ```tsx interface Student { studentId: number; name: string; grade?: number; } // 正確 let alice: Student = { studentId: 102, name: 'Alice', grade: 90 }; // 正確 let lily: Student = { studentId: 103, name: 'Lily' }; ``` ### 任意屬性 如果想讓介面可以接受其他任何名稱的屬性,可以這樣寫: ```tsx interface Student { studentId: number; name: string; grade?: number; [propName: string]: any; } let john: Student = { studentId: 104, name: 'John', department: 'History' }; ``` 用 **`[propName: string]`** 表示除了已定義的屬性外,介面還可以接受任何其他的額外屬性。 ### 唯讀屬性 如果某些屬性在建立後就不想讓它改變,可以用 **`readonly`** : ```tsx interface Student { readonly studentId: number; name: string; } let mike: Student = { studentId: 105, name: 'Mike' }; // mike.studentId = 106; // 這行會報錯,因為 studentId 是唯讀的 ``` 像是上面 **`studentId`** 被設定為 **`readonly`**,就不能再更改。 ## 常用情境 1. **定義物件的形狀(Shape)**:當你需要預先定義一個物件應該包含哪些屬性和方法時,介面是非常合適的選擇。特別是在大型項目或團隊協作中。 2. **強化函式參數的型別檢查**:當你需要函式接受帶有特定屬性的物件時,可以使用介面來定義這些屬性,來提高程式碼的可讀性和可維護性。 3. **定義複雜的資料結構**:當處理複雜的資料結構,例如從 API 取得的大型 JSON 物件時,介面可以幫助你管理和理解這些資料結構的形狀,不用每次都需要通靈。 ## 情境一:定義物件的形狀(Shape) ### **範例情境:使用者資訊管理系統** 在這個系統需要處理使用者資訊,像是姓名、年齡和電子郵件地址。為了確保在整個應用程式中一致處理這些資訊,我們可以用介面來定義一個「使用者」的形狀。 ### 定義「使用者」介面 ```tsx // 重點:定義使用者的形狀 interface User { name: string; age: number; email: string; } function displayUser(user: User) { console.log(`Name: ${user.name}, Age: ${user.age}, Email: ${user.email}`); } let user1: User = { name: "陳大明", age: 30, email: "damingchen@example.com" }; displayUser(user1); ``` 透過這樣的方式確保在整個應用程式中,所有處理使用者資訊的地方都會遵循同一個結構和型別,藉此提高程式碼的一致性和可靠性。 ## 情境二:強化函式參數的型別檢查 ### **範例情境:商品庫存管理系統** 一個商品庫存管理系統。我們需要處理各種商品的庫存狀況,包括商品名稱、數量和價格。為了確保函式能夠接收正確格式的商品資訊,我們可以用介面來強化函式參數的型別檢查。 ### 定義「商品」介面 ```tsx= interface Product { id: string, name: string, quantity: number, price: number } // 重點:參數在型別註釋,使用 Product 介面 function updateInventory(product: Product, additionalQuantity: number) { product.quantity += additionalQuantity; console.log(`更新 ${product.name}: ${product.quantity} 品項到庫存`); } let newProduct: Product = { id: "A105", name: "筆記型電腦", quantity: 20, price: 45000 }; // 新增特定產品庫存量 updateInventory(newProduct, 5); ``` 透過使用介面來強化函式參數的型別檢查,確保函式只接受符合特定結構的物件,這樣一來可以減少錯誤,又能提高代碼的可靠性 :D ## 情境三:定義複雜的資料結構 ### **範例情境:線上課程平台的課程資訊管理** 假設六角學院正在開發一個線上課程平台,需要處理各種課程的詳細資訊,包括課程名稱、講師資訊、課程內容和學生評價等。由於這些資料結構相對複雜,就適合用介面來定義這些結構。 ### 定義相關介面 ```tsx= // 講師介面 interface Instructor { name: string; expertise: string[]; } // 課程介面,重要,有發現到這裡用到了 Instructor 介面嗎? interface Course { title: string; description: string; instructor: Instructor; rating: number; reviews: string[]; } // 顯示課程資訊 function displayCourseInfo(course: Course) { console.log(`課程名稱: ${course.title}`); console.log(`課程描述: ${course.description}`); console.log(`講者名稱: ${course.instructor.name}`); console.log(`課程評價: ${course.rating}/5`); } let programmingCourse: Course = { title: "進階 TypeScript 程式設計", description: "深入瞭解 TypeScript 的高階特性。", instructor: { name: "王小明", expertise: ["TypeScript", "JavaScript", "軟體工程"] }, rating: 4.5, reviews: ["很實用的課程!", "深入淺出,易於理解。"] }; displayCourseInfo(programmingCourse); ``` 像是這範例中,**`Instructor`** 介面定義了講師的姓名 (**`name`**) 和其專長領域 (**`expertise`**)。當 **`Course`** 介面中引用 **`Instructor`** 介面時,就表明每個課程都會有一位講師,並且這位講師的資訊會遵循 **`Instructor`** 介面的結構。 ### 這樣的好處是在 1. **模組化和重用性**:通過將 **`Instructor`** 作為一個獨立的介面,我們可以在很多地方重用這個介面。這種模組化方法讓程式碼更加清晰,並減少重複性。 2. **清晰的結構定義**:將講師的資訊封裝在自己的介面中,可以讓 **`Course`** 介面的結構更加清晰。 3. **易於維護和擴充**:如果未來講師的資訊需要添加新的屬性或修改,我們只需更改 **`Instructor`** 介面。這種設計讓整個系統更容易維護和擴充。 ## 單選題 1. **在 TypeScript 中,interface 主要用於:** A. 定義函數的返回值 B. 儲存資料 C. 定義物件的形狀 D. 計算數值 2. **在 TypeScript 的 interface 中,可選屬性是如何表示的?** A. 使用前綴符號 **`?`** B. 使用前綴符號 **`#`** C. 使用後綴符號 **`!`** D. 使用後綴符號 **`^`** 3. **TypeScript 的 interface 可以用於哪種情況?** A. 定義一個變數的數值 B. 管理複雜的資料結構 C. 進行數學運算 D. 設定程式的背景顏色 ## 實作題 ### 醫院病患和預約管理系統 #### 任務描述 1. **定義介面** - **`Patient`** 介面:應包含病患的姓名(**`name`**)、年齡(**`age`**)、性別(**`gender`**)和病歷號碼(**`medicalRecordNumber`**)。 - **`Appointment`** 介面:應包含預約的日期(**`date`**)、時間(**`time`**)、醫生姓名(**`doctorName`**)和病患病歷號碼(**`patientMedicalRecordNumber`**),用來關聯病患的病歷號碼。 2. **實作資料結構** - 建立一個 **`patients`** 陣列,用於儲存 **`Patient`** 物件。 - 建立一個 **`appointments`** 陣列,用於儲存 **`Appointment`** 物件。 3. **開發功能** - 實作函式 **`addPatient`** 來添加新病患到 **`patients`** 陣列。 - 實作函式 **`scheduleAppointment`** 來為存在的病患安排新的預約,並加入到 **`appointments`** 陣列。 - 實作函式 **`cancelAppointment`** 來取消現有的預約。 - 實作函式 **`listAppointments`** 來列出特定病患的所有預約。 #### 部分程式碼 ```tsx // 定義 Patient 病患介面 interface Patient { name: string; age: number; gender: string; medicalRecordNumber: string; } // 定義 Appointment 預約介面 interface Appointment { date: string; time: string; doctorName: string; patientMedicalRecordNumber: string; } // 病患資料陣列 let patients: Patient[] = []; // 預約資料陣列 let appointments: Appointment[] = []; // 實作函式 addPatient function addPatient(patient: Patient): void { patients.push(patient); } // 實作函式 scheduleAppointment function scheduleAppointment(appointment: Appointment): void { // 確保病患存在 const patientExists = patients.some( (patient) => patient.medicalRecordNumber === appointment.patientMedicalRecordNumber ); if (!patientExists) { throw new Error("病患不存在"); } // TODO: 增加一筆預約到預約列表 } // TODO: 函式 cancelAppointment // TODO: 實作函式 listAppointments // 增加病患 addPatient({ name: "John Doe", age: 30, gender: "Male", medicalRecordNumber: "12345" }); // 安排預約 scheduleAppointment({ date: "2024-01-15", time: "10:00", doctorName: "Dr. Smith", patientMedicalRecordNumber: "12345" }); ``` <!-- 解答: 單選題:C、A、B 實作題:https://codepen.io/zeoxer/pen/ByjeJmd -->