# 🏅 Day 7 - 介面(interfaces)
### 介面是啥?
在 TypeScript 裡頭,介面(Interface)是用來定義物件的型別。
在不使用 Interfaces 以前,我們都必須用型別註釋來定義物件參數,內容會寫得很冗長。但 Interfaces 可以有效解決這個問題。
### 基本範例
```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`** 被設定,就不能再更改。
## 常用情境
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`** 介面。這種設計讓整個系統更容易維護和擴充。
<span style="color:blue">學到這裡,相信你開始可以理解,其實學 TypeScript 其中一個主要原因,就是為了讓資料結構上的可讀性、可維護性變得更高。
</span>
<span style="color:blue">所以正在學 TypeScript 的你/妳,在挽起袖子準備寫程式前,先把後端工程師的 API 資料結構拿去分析,並思考哪些內容可以拆出適當的 TypeScript 型別,有助於你開發上變得更順利 🙌</span>
## 單選題
1. **在 TypeScript 中,interface 主要用於:**
A. 定義函數的返回值
B. 儲存資料
C. 定義物件的形狀
D. 計算數值
2. **在 TypeScript 的 interface 中,可選屬性是如何表示的?**
A. 使用前綴符號 **`?`**
B. 使用前綴符號 **`#`**
C. 使用後綴符號 **`!`**
D. 使用後綴符號 **`^`**
3. **TypeScript 的 interface 可以用於哪種情況?**
A. 定義一個變數的數值
B. 管理複雜的資料結構
C. 進行數學運算
D. 設定程式的背景顏色
## 開發任務:醫院病患和預約管理系統
### 目標
**前工程師寫到一半烙跑了**,所以你才有機會得到這份工作~ヽ(́◕◞౪◟◕‵)ノ
**你的任務是接手他的糙 Code,來設計醫院病患和預約管理系統。**
### 任務描述
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" });
```
## 回報流程
將答案寫在 CodePen,並貼至底下回報就算完成了喔!
解答位置請參考下圖(需打開程式碼的部分觀看)

<!-- 解答:
單選題目:C、A、B
-->
回報區
---
| Discord | CodePen / 答案 |
|:-------------:|:----------------------------------------------------------------:|
|洧杰|[Codepen](https://codepen.io/hexschool/pen/poYgYqW?editors=1010)|
|神奇海螺|[Codepen](https://codepen.io/hexschool/pen/poYgYqW?editors=1010)|
|ZS|[Codepen](https://codepen.io/irishuang/pen/MWxJXQV)|
|HsienLu|[Codepen](https://codepen.io/Hsienlu/pen/vYPgrmB)|
|YC|[HackMD](https://hackmd.io/SKoJd3EsTlitnjzCx4Rarg?view)|
|BonnieChan|[Codepen](https://codepen.io/Bonnie-chan-the-bold/pen/oNVByVN?editors=1012)|
|苡安|[Codepen](https://codepen.io/yi-an-yang/pen/MWxJXor)|
|LinaChen|[Codepen](https://codepen.io/LinaChen/pen/bGZgmRB)|
|Jack|[Codepen](https://codepen.io/lj787448952/pen/YzgNRXq)|
| hannahpun|[Codepen](https://codepen.io/hannahpun/pen/ZEPLmjg)|
|hannahTW|[Codepen](https://codepen.io/hangineer/pen/poYRKqL?editors=0011)|
|Henry_Wu|[Codepen](https://codepen.io/hekman1122/pen/JjzExeq?editors=1011)|
|展誠|[Codepen](https://codepen.io/hedgehogkucc/pen/LYaxoPJ?editors=1012)|
|Bryan Chu|[CodePen](https://codepen.io/bryanchu10/pen/JjzEQXj)|
|薏慈|[CodePen](https://codepen.io/its_wang/pen/poYRXoK?editors=1010)|
|Mia Tsai|[CodePen](https://codepen.io/Mianzi/pen/yLwgmYL)|
|jasperlu005|[Codepen](https://codepen.io/uzzakuyr-the-reactor/pen/ZEPLgrp?editors=1011)|
|rikku1756|[Codepen](https://codepen.io/rikkubook/pen/wvOgVEV?editors=1111)|
|Mi|[CodePen](https://codepen.io/Mi-Jou-Hsieh/pen/dyrNxvJ?editors=1010)|
|deedee1215|[CodePen](https://codepen.io/diddy032/pen/jOJygQW)|
|Alyce|[CodePen](https://codepen.io/alycehwy/pen/ZEPezvr)|
|Kai|[CodePen](https://codepen.io/kaiyuncheng-the-styleful/pen/GReWJQJ?editors=0012)|
|yunhung|[CodePen](https://codepen.io/ahung888/pen/JjzWGgv)|
|77_0411|[CodePen](https://codepen.io/chung-chi/pen/bGZgrEJ?editors=1010)|
|hiYifang|[HackMD](https://hackmd.io/@gPeowpvtQX2Om6AmD-s3xw/SkhePfMKa)|
|Amberhh| [codepen](https://codepen.io/Amberhh/pen/poYebjK?editors=1011)|
|連小艾|[Codepen](https://codepen.io/bolaslien/pen/zYbZBqX?editors=1011)|
|m_m|[CodePen](https://codepen.io/minnn7716/pen/MWxpbYp)|
|Yang|[Codepen](https://codepen.io/Yang-J/pen/WNmRPqX)|
|clairechang|[Notion](https://claire-chang.notion.site/Day-7-interfaces-62d98123e2994bbab3a058e7f1749fb3)|
|JC|[Codepen](https://codepen.io/jcsamoyed/pen/vYPxKoe?editors=0012)
|Zuo|[Codepen](https://codepen.io/linchinhsuan/pen/RwdpZgy?editors=1010)
|Otis|[CodePen](https://codepen.io/humming74/pen/eYXvEQB?editors=1010)|
|Lisa|[CodePen](https://codepen.io/lisaha/pen/PoLpzYW?editors=1012)|
|wendy_.li|[HACKMD](https://hackmd.io/PcmFgqZwRd-4Ep3-LgK5_Q)
|Starr|[CodePen](https://codepen.io/StarrZhong/pen/zYbZdbz)
|NiuNiu|[CodePen](https://codepen.io/Dawson-the-bold/pen/OJqpNRP?editors=0011)
|翰毅|[CodePen](https://codepen.io/yzuigtdw-the-animator/pen/WNmpdoW?editors=1111)|
| fanshu0303 | [CodePen](https://codepen.io/JuiHsuanLee0303/pen/OJqpeKv) |
| Teddy | [CodePen](https://codepen.io/TaideLi/pen/ExMmyWM) |
|wei|[CodePen](https://codepen.io/jweeei/pen/NWJjNWK?editors=1011)|
|erwin阿瀚|[CodePen](https://codepen.io/yohey03518/pen/LYaywNN)|
|精靈|[CodePen](https://codepen.io/justafairy/pen/NWJgjYP)|
|shan13|[CodePen](https://codepen.io/yishan13-tsai/pen/KKEqvLO)|
|皓皓|[HackMD](https://hackmd.io/@cutecat8110/Sy5JVpPt6)|
|小米|[HackMD](https://codepen.io/joanne-wei/pen/qBvjxWm?editors=0010)|
|ethan1331|[CodePen](https://codepen.io/EthanTsai/pen/MWxoZmx)|
|Snorlax|[HackMD](https://hackmd.io/@snorlaxpock/BkjOf2jK6)|
|Yoshi|[CodePen](https://codepen.io/yoshiyyc/pen/YzgYPLK)|
|Nick Lin|[CodePen](https://codepen.io/NickLinP/pen/ZEPvYKv?editors=1111)|
|Rochel|[Codepen](https://codepen.io/rochelwang1205/pen/xxBWRJN?editors=1012)|
|leave3310|[Codepen](https://codepen.io/leave3310-the-looper/pen/LYaerZY?editors=0011)|
|aki|[codepen](https://codepen.io/aki168/pen/abMKOyG)|
|Tori|[Hackmd](https://hackmd.io/OAdkiOH-S_WkD-LF6IONVA?view)|
puffy|[Codepen](https://codepen.io/TernMayDay/pen/bGZZLXY?editors=0010)
| 我是泇吟 | [Codepen](https://codepen.io/kljuqbxs/pen/zYVQZpZ) |