iT邦幫忙

2023 iThome 鐵人賽

DAY 23
0

類別封裝是一種將類別的內部細節隱藏起來,確保類別的屬性和方法在外部程式碼中不被隨意訪問,提高程式碼的可維護性和可讀性,同時提供了一定程度的安全性。

存取修飾符 ( Access Modifiers )

類別修飾符是指定類別的屬性和方法是否有對外使用權限的關鍵字。它讓我們控制哪些屬性和方法只能在類別內部使用,哪些可以在外部使用,以及哪些可以被子類別繼承。

下面是 TypeScript 中的主要修飾符:

  • public ( 公開 )

預設 的修飾符,表示在不寫任何修飾符的情況下,都將代表著 public 修飾符。類別公開的屬性和方法可以在外部中被使用

看以下範例:

class Human {
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

class Person extends Human {
  getName(): void {
    console.log(this.name); // 可以直接使用父類別的 name 屬性
  }
}

const person = new Person("威爾豬");
person.getName(); // 輸出:威爾豬
console.log(person.name); // 輸出:威爾豬 ⭕️ 外部可以使用 name 屬性

在這個例子中,name 屬性使用了預設修飾符 ( public ),因此可以在類別外部直接使用。

我們也可以使用 **參數屬性** 來簡化參數賦值給類別屬性的動作。因為在建構函式中將參數賦值給屬性需要額外的程式碼,使用參數屬性可以減少這種重複性的工作。

class Human {
  // 參數屬性自動將參數值賦值給對應的屬性
  constructor(public name: string) {}
}

class Person extends Human {
  getName(): void {
    console.log(this.name);
  }
}

const person = new Person("威爾豬");
person.getName(); // 輸出:威爾豬
console.log(person.name); // 輸出:威爾豬

注意:public 修飾符就必須要寫出,不然會 TypeScript 會報錯,導致編譯出來的結果為 undefined。

https://ithelp.ithome.com.tw/upload/images/20230923/20141250YfGc9A795M.png

  • private ( 私有 )

私有的屬性和方法只能在該類別內部使用,外部無法使用。

看以下範例:

class Animal {
  private _name: string;

  constructor(name: string) {
    this._name = name;
  }
}

class Cat extends Animal {
  showName(): void {
    console.log(`這隻貓貓的名字是${this._name}`); // ❌ 無法使用父類別的私有屬性
  }
}

在這個例子中,Animal 類別的 name 屬性被聲明為私有屬性,只能在該類別內部訪問。Cat 類別繼承了 Animal 並試圖訪問父類別的私有屬性,這將導致編譯錯誤。

https://ithelp.ithome.com.tw/upload/images/20230923/20141250Ls75PFAzA9.png

我們看一下 MDN 的一段話:

https://ithelp.ithome.com.tw/upload/images/20230923/20141250DMLcPby3gr.png

這段話表示我們可以將 private 改成在前綴使用 # 來代替。

順便修改一下上面的範例讓錯誤消失:

class Cat {
  // 修改 private 為 # 符號
  #name: string;

  constructor(name: string) {
    this.#name = name;
  }

  showName(): void {
    console.log(`這隻貓貓的名字是${this.#name}`);
  }
}

const myCat = new Cat("旺妞");
myCat.showName(); //輸出: 這隻貓貓的名字是旺妞

當然我們也可以使用 參數屬性 就可以了:

class Cat {
  constructor(private _name: string) {}

  showName(): void {
    console.log(`這隻貓貓的名字是${this._name}`);
  }
}

const myCat = new Cat("旺妞");
myCat.showName(); //輸出: 這隻貓貓的名字是旺妞
  • protected ( 受保護的 )

protected 與 private 相似,但有一個重要的區別,受 保護的屬性和方法可以在類別內部和繼承該類別的子類別中使用

看以下範例:

class Car {
  constructor(protected speed: number) {}

  addSpeed(amount: number) {
    this.speed += amount;
  }
}

class Bus extends Car {
  nowSpeed() {
    console.log(`現在時速 ${this.speed}`); // 可以使用父類別的 speed 屬性
  }
}

const bus = new Bus(60);

bus.nowSpeed(); // 輸出: 現在時速 60
bus.addSpeed(20);
bus.nowSpeed(); // 輸出: 現在時速 80
console.log(bus.speed); // ❌ 外部不能使用 speed 屬性

在這個例子中,speed 屬性使用了 protected 修飾符,所以它可以在 Car 類別的子類別 Bus 中使用,但在外部是無法使用的。

https://ithelp.ithome.com.tw/upload/images/20230923/20141250eePX51K0fP.png

  • readonly ( 只能讀取 )

用於將類別的屬性和方法設置為只能讀取,一旦初始化後就不能再被修改。

看以下範例:

class Car {
  constructor(readonly brand: string, readonly model: string) {}
}

const myCar = new Car("Ferrari", "LaFerrari");
myCar.brand = "Bugatti"; // ❌ 無法修改 readonly 屬性

brand 和 model 屬性使用了 readonly 修飾符,所以只能讀取,不能再進行修改。

https://ithelp.ithome.com.tw/upload/images/20230923/20141250gq0rGGrcts.png

不過要注意的是,修飾符在 TypeScript 中主要是用來進行 靜態類型檢查,而不是在運行時實施的嚴格封裝。所以即便使用了 private、protected、readonly 等存取修飾符的屬性或方法,雖然 Typescript 會跳出錯誤警告,但它還是會正常編譯的。小夥伴們應該還記得如何讓 TypeScript 報錯就不進行編譯吧。( 忘記了就來看配置文件吧! )

類別修飾符 ( Class Modifiers )

類別修飾符用於修改類別本身的特性,並不影響類別屬性和方法的可見性或使用權限。

  • abstract ( 抽象 )

用於聲明抽象類別或抽象方法,不允許實體化,但可以被其他類別來繼承,通常用於 定義共用的屬性和方法。( 在 類別 章節有說明。 )

  • static ( 靜態 )

用於定義類別的靜態屬性和方法,這些屬性和方法屬於類別本身,可以在類別的內部直接使用,不需要另外創建類別的實體 / 實例 (Instance)。

看以下範例:

class Me {
  static firstName: string;

  static add(lastName: string): string {
    return this.firstName + lastName;
  }
}

Me.firstName = "威爾";
console.log(Me.add("豬")); // 輸出: 威爾豬

在這個例子中,我們定義了一個 Me 類別,包含一個靜態屬性 firstName 和一個靜態方法 add,它們可以直接通過類別名稱訪問,不需要再創建類別的實體。

當然我們還可以加上存取修飾符:

class Me {
  // 改成靜態私有屬性
  static #firstName: string;

  static add(lastName: string): string {
    return this.#firstName + lastName;
  }
}

Me.#firstName = "威爾"; // ❌ 無法在外部使用私有屬性

https://ithelp.ithome.com.tw/upload/images/20230923/20141250NxushyVHnW.png

  • export ( 匯出 )

用於將類別或模組 ( 如函式、變數等 ) 匯出,以便在其他檔案中引入 ( import ) 使用。

// Person.ts

export class Person {
  constructor(private _name: string, private _age: number) {}

  greet(): string {
    return `我是${this._name},今年 ${this._age} 歲`;
  }
}
// app.ts

import { Person } from "./Person";

const person1 = new Person("威爾豬", 3);
const person2 = new Person("威爾羊", 5);

console.log(person1.greet()); // 輸出: 我是威爾豬,今年 3 歲
console.log(person2.greet()); // 輸出: 我是威爾羊,今年 5 歲

在這個範例中,我們使用 export 將 Person 類別匯出,然後在 app.ts 中使用 import 將它導入。這樣就可以在不同檔案中使用相同的類別了。

Getters / Setters

Getters 和 Setters 可以讓我們在取得或設置屬性值時,執行自定義的邏輯,而不僅僅是直接使用或單純設定屬性而已。

看以下範例:

class Person {
  constructor(private _name: string) {}

  get name(): string {
    return this._name;
  }

  set name(value: string) {
    value.trim() === "" ? console.log("名字不能為空") : (this._name = value);
  }

  getName(): string {
    return `我是${this._name}`;
  }
}

const person = new Person("威爾豬");

// 使用 getter 取得屬性
console.log(person.name); // 輸出: 威爾豬

// 使用 setter 設定屬性
person.name = ""; // 輸出: 名字不能為空

console.log(person.getName()); // 輸出: 我是威爾豬

這個範例中,我們創建了一個 Person 類別,其中有一個私有屬性 _name。當我們使用 person.name 時,實際上是調用了 getter 方法,而在將 person.name 賦予新值時,實際上是調用了 setter 方法。


我們可以使用上面的存取修飾符、類別修飾符和 Getter / Setter 等來設置類別裡屬性和方法的使用權限,將類別的內部細節隱藏起來,只向外部公開必要的接口。通過合理運用這些修飾符,我們可以創建出結構化、安全性高且易於維護的程式碼,同時保證了類別裡屬性和方法的使用和修改受到適當的限制。


上一篇
類別 ( Classes )
下一篇
模組 & 命名空間 ( Modules & Namespaces )
系列文
用不到 30 天學會基本 TypeScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言