# 🏅 Day 9 - interfaces 延伸功能介紹
## 介面擴展(Interface Extending)
介面有一個 extend 功能來達成介面擴展。可以允許一個介面繼承另一個介面的屬性和方法。這是物件導向程式設計中常見的繼承概念的一種實現。
當你想要建立一個新的介面,並且這個新介面要包含某個已存在介面的所有特性時就能夠派上用場~
### **使用情境一:應用程式帳號權限管理**
1. 一個應用程式需要定義不同種類的使用者。
2. 每個使用者都有一些共通的屬性,如姓名和電子郵件地址。但是,根據使用者的類型(如管理員、訪客等),他們還會有一些額外的特定屬性。
3. 在這情境中,我們可以用一個基本的 **`User`** 介面,然後讓其他更具體的介面繼承這個 **`User`** 介面。
#### **範例程式碼**
```tsx=
// 基本的 User 介面
interface User {
name: string;
email: string;
}
// 管理員介面,擴展 User 介面,使用 isAdmin 屬性
interface Admin extends User {
isAdmin: boolean;
}
// 訪客介面,擴展 User 介面
interface Guest extends User {
guestSince: Date;
}
// 使用 Admin 介面
const adminUser: Admin = {
name: "Alice",
email: "alice@example.com",
isAdmin: true
};
// 使用 Guest 介面
const guestUser: Guest = {
name: "Bob",
email: "bob@example.com",
guestSince: new Date()
};
```
1. 在這個情境,**`Admin`** 和 **`Guest`** 都擴展了 **`User`** 介面。
2. 代表它們都繼承了 **`User`** 的 **`name`** 和 **`email`** 屬性
3. 並且添加了它們自己的特定屬性:**`Admin`** 有一個 **`isAdmin`** 屬性,而 **`Guest`** 有一個 **`guestSince`** 屬性。
4. 這樣我們就可以根據不同的使用者類型,來建立具有特定屬性的物件了!
### 使用情境二:車輛規格管理
以車輛來說,每台車都有一些共通的屬性,例如品牌和製造年份。
但具備的功能可能會不同,例如有些是吃汽油、有些是電動汽車。
根據車輛的類型,它們還會有一些額外的特定屬性。我們可以使用一個基本的 Vehicle 介面,然後讓其他更具體的介面繼承這個 Vehicle 介面。
#### 基本車輛介面
- 所有車輛共有的屬性:品牌(brand)、製造年份(year)。
#### 汽車介面
- 特定屬性:座位數(seats)、車型(type)。
#### 卡車介面
- 特定屬性:載重量(loadCapacity)。
#### 電動汽車介面
- 特定屬性:電池容量(batteryCapacity)、續航里程(range)。
### **範例程式碼**
```tsx=
// 基本車輛介面
interface Vehicle {
brand: string;
year: number;
}
// 汽車介面
interface Car extends Vehicle {
seats: number;
type: string;
}
// 卡車介面
interface Truck extends Vehicle {
loadCapacity: number;
}
// 電動汽車介面
interface ElectricCar extends Vehicle {
batteryCapacity: number;
range: number;
}
// 使用 Car 介面
const car: Car = {
brand: "Toyota",
year: 2020,
seats: 5,
type: "Sedan"
};
// 使用 Truck 介面
const truck: Truck = {
brand: "Ford",
year: 2018,
loadCapacity: 2000
};
// 使用 ElectricCar 介面
const electricCar: ElectricCar = {
brand: "Tesla",
year: 2021,
batteryCapacity: 75,
range: 300
};
```
1. **`Car`**、**`Truck`** 和 **`ElectricCar`** 介面都擴展了 **`Vehicle`** 介面
2. 讓它們能夠繼承 **`brand`** 和 **`year`** 屬性,同時也各自增加了特定於它們類型的屬性。
3. 除了讓程式更具可讀性和可維護性,同時也保證了資料結構的一致性 :D
## 介面合併(Interface merging)
介面合併允許開發者將**同名的介面宣告合併成一個介面**。這個特性在處理來自多個地方的介面擴展時特別有用
在添加額外屬性到現有介面或在使用第三方套件時很有幫助!
先來上個範例程式碼,主要是定義各種數位產品的特性:
```tsx=
// 初始 DigitalDevice 介面定義了產品的價格
interface DigitalDevice {
price: number;
}
// 擴展 DigitalDevice 介面以包含產品的重量
interface DigitalDevice {
weight: number;
}
/*
介面中的屬性在合併時會簡單的合併到一個介面中,相當於:
interface DigitalDevice {
price: number;
weight: number;
}
*/
// 使用合併後的 DigitalDevice 介面
const myDevice: DigitalDevice = {
price: 299.99,
weight: 1.5
};
// 函式來演示如何使用 DigitalDevice
function displayDeviceDetails(device: DigitalDevice) {
console.log(`載具價格: ${device.price}, 載具重量: ${device.weight} kg`);
}
// 顯示產品細節
displayDeviceDetails(myDevice);
```
接著我們再透過較常見的情境來進行講解~
### **使用情境:擴展第三方套件型別**
假使你正在使用一個第三方的 UI 套件,這個套件提供一個 **`Button`** 介面來描述按鈕屬性。
假使你想要替這個 **`Button`** 介面添加一些自定義屬性,例如 **`dataTestId`** 用來測試的一個特殊屬性。
以下是如何在 **`app.ts`** 中擴展 **`button.ts`** 中定義的 **`Button`** 介面的範例:
#### **button.ts**
```tsx=
// 第三方套件或另一個檔案中的 Button 介面
export interface Button {
text: string;
onClick: () => void;
}
```
#### **extendedButton.ts(你的應用程式想客製化的功能)**
```tsx=
// 引入原始的 Button 介面
import { Button } from './button';
// 在你的應用程式中擴展 Button 介面
declare module './button' {
interface Button {
dataTestId?: string;
}
}
```
#### **app.ts**
```tsx
// 載入第三方按鈕套件
import { Button } from './button';
// 在 app.ts 中擴展 Button 介面
import './extendedButton';
// 現在 Button 介面就有包含 text, onClick 和 dataTestId 屬性嘍
const myButton: Button = {
text: "Click Me",
onClick: () => console.log("Button clicked"),
dataTestId: "my-test-id"
};
// 函式來演示如何使用 Button
function renderButton(button: Button) {
// 模擬渲染,僅印出 console
console.log(`顯示按鈕 - 文字: ${button.text}, 資料測試 ID: ${button.dataTestId}`);
}
// 渲染按鈕
renderButton(myButton);
```
這種方法的優點是,**不需要在原始的介面定義檔案中進行任何更改,就可以靈活擴展介面**
## 單選題
1. 哪一個關鍵字用於 TypeScript 中的介面擴展?
A. **`merge`**
B. **`extend`**
C. **`implements`**
D. **`combine`**
2. 當兩個擁有相同名稱的介面出現在同一作用域中時,TypeScript 會怎麼做?
A. 產生錯誤
B. 忽略其中一個
C. 合併它們
D. 重命名它們
3. 在 TypeScript 中,介面合併(Interface Merging)通常用於什麼情況?
A. 當需要重新命名 interface 時
B. 當需要向現有介面添加更多屬性
C. 當需要創建全新的介面
4. 下面哪種說法正確描述了 TypeScript 中的介面擴展(Interface Extending)?
A. 它允許一個介面從另一個介面繼承屬性和方法。
B. 它允許合併兩個具有不同名稱的介面。
5. 何者無法透過 interface 直接實現?
A. 物件結構描述
B. 聯合型別
C. 多層擴充
D. 類別實作
6. 下列哪個描述正確?
A. **`interface`** 可重複宣告
B. **`type`** 支援合併宣告
C. **`interface`** 僅能用於函式
## 實作題
1. 建立一個 **`Person`** 介面包含姓名 (**`name`**)、年齡(**`age`**) 和職業(**`career`**) 屬性,以及一個 **`ContactInfo`** 介面包含地址(**`address`**)、電話(**`phone`**) 和信箱(**`email`**) 屬性,最後建立一個 **`Member`** 介面來擴充 **`Person`** 和 **`ContactInfo`** 兩個介面,並加上一個 **`id`** 屬性。
<!-- 解答:
單選題:B、C、B、A、B、A
實作題:
interface Person {
name: string;
age: number;
career: string;
}
interface ContactInfo {
address: string;
phone: string;
email: string;
}
interface Member extends Person, ContactInfo {
id: number (或是 string 也可以)
}
-->