TypeScript 類別 (Class)
TypeScript 完整支援 ES6 的類別語法,並在此基礎上加入了型別標註和存取修飾子等功能,讓物件導向程式設計更加完善。
基本類別定義
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): string {
return `你好,我是 ${this.name},今年 ${this.age} 歲`;
}
}
const person = new Person('小明', 25);
console.log(person.greet()); // 你好,我是小明,今年 25 歲
存取修飾子 (Access Modifiers)
TypeScript 提供三種存取修飾子:
public (公開)
預設值,可以從任何地方存取:
class Animal {
public name: string;
public constructor(name: string) {
this.name = name;
}
public speak(): void {
console.log(`${this.name} makes a sound`);
}
}
private (私有)
只能在類別內部存取:
class BankAccount {
private balance: number;
constructor(initialBalance: number) {
this.balance = initialBalance;
}
deposit(amount: number): void {
this.balance += amount;
}
withdraw(amount: number): boolean {
if (amount <= this.balance) {
this.balance -= amount;
return true;
}
return false;
}
getBalance(): number {
return this.balance;
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
// account.balance; // 錯誤:'balance' 是私有屬性
protected (受保護)
只能在類別內部和子類別中存取:
class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
bark(): void {
console.log(`${this.name} says: 汪汪!`); // OK,可以存取 protected
}
}
const dog = new Dog('小黑');
dog.bark(); // 小黑 says: 汪汪!
// dog.name; // 錯誤:'name' 是受保護屬性
參數屬性 (Parameter Properties)
TypeScript 提供簡化語法,在建構子參數前加上修飾子:
// 傳統寫法
class Person {
private name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
// 簡化寫法
class Person {
constructor(
private name: string,
private age: number
) {}
getInfo(): string {
return `${this.name}, ${this.age} 歲`;
}
}
唯讀屬性 (readonly)
class Point {
readonly x: number;
readonly y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
const point = new Point(10, 20);
// point.x = 30; // 錯誤:無法指派給 'x',因為它是唯讀屬性
結合參數屬性:
class Point {
constructor(
readonly x: number,
readonly y: number
) {}
}
Getter 和 Setter
class Circle {
private _radius: number;
constructor(radius: number) {
this._radius = radius;
}
get radius(): number {
return this._radius;
}
set radius(value: number) {
if (value <= 0) {
throw new Error('Radius must be positive');
}
this._radius = value;
}
get area(): number {
return Math.PI * this._radius ** 2;
}
}
const circle = new Circle(5);
console.log(circle.radius); // 5
console.log(circle.area); // 78.54...
circle.radius = 10;
console.log(circle.area); // 314.16...
靜態成員 (Static Members)
靜態成員屬於類別本身,而非實例:
class MathUtils {
static PI = 3.14159;
static add(a: number, b: number): number {
return a + b;
}
static multiply(a: number, b: number): number {
return a * b;
}
}
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.add(2, 3)); // 5
// 不能透過實例存取
// const utils = new MathUtils();
// utils.PI; // 錯誤
靜態區塊 (Static Blocks)
TypeScript 4.4+ 支援靜態初始化區塊:
class Database {
static connection: any;
static {
// 靜態初始化程式碼
this.connection = createConnection();
console.log('Database initialized');
}
}
類別繼承 (Inheritance)
使用 extends 繼承類別:
class Animal {
constructor(protected name: string) {}
speak(): void {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
constructor(
name: string,
private breed: string
) {
super(name); // 呼叫父類別建構子
}
speak(): void {
console.log(`${this.name} barks!`);
}
getBreed(): string {
return this.breed;
}
}
class Cat extends Animal {
speak(): void {
console.log(`${this.name} meows!`);
}
}
const dog = new Dog('小黑', '柴犬');
dog.speak(); // 小黑 barks!
const cat = new Cat('小花');
cat.speak(); // 小花 meows!
實作介面 (implements)
interface Printable {
print(): void;
}
interface Savable {
save(): void;
}
class Document implements Printable, Savable {
constructor(private content: string) {}
print(): void {
console.log(`Printing: ${this.content}`);
}
save(): void {
console.log(`Saving: ${this.content}`);
}
}
泛型類別
class Container<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
get(index: number): T | undefined {
return this.items[index];
}
getAll(): T[] {
return [...this.items];
}
}
const numberContainer = new Container<number>();
numberContainer.add(1);
numberContainer.add(2);
const stringContainer = new Container<string>();
stringContainer.add('hello');
stringContainer.add('world');
this 型別
class Builder {
private value: number = 0;
add(n: number): this {
this.value += n;
return this;
}
multiply(n: number): this {
this.value *= n;
return this;
}
getValue(): number {
return this.value;
}
}
// 鏈式呼叫
const result = new Builder().add(5).multiply(2).add(3).getValue(); // 13
類別表達式
const Rectangle = class {
constructor(
public width: number,
public height: number
) {}
getArea(): number {
return this.width * this.height;
}
};
const rect = new Rectangle(10, 20);
console.log(rect.getArea()); // 200
override 關鍵字
TypeScript 4.3+ 提供 override 關鍵字確保正確覆寫父類別方法:
class Animal {
speak(): void {
console.log('Animal speaks');
}
}
class Dog extends Animal {
override speak(): void {
console.log('Dog barks');
}
// override invalidMethod(): void {} // 錯誤:父類別沒有這個方法
}
需要在 tsconfig.json 中啟用 noImplicitOverride:
{
"compilerOptions": {
"noImplicitOverride": true
}
}
實用範例
單例模式 (Singleton)
class Singleton {
private static instance: Singleton;
private constructor() {}
static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2); // true
工廠模式
abstract class Shape {
abstract getArea(): number;
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
getArea(): number {
return Math.PI * this.radius ** 2;
}
}
class Rectangle extends Shape {
constructor(
private width: number,
private height: number
) {
super();
}
getArea(): number {
return this.width * this.height;
}
}
class ShapeFactory {
static createCircle(radius: number): Circle {
return new Circle(radius);
}
static createRectangle(width: number, height: number): Rectangle {
return new Rectangle(width, height);
}
}
const circle = ShapeFactory.createCircle(5);
const rectangle = ShapeFactory.createRectangle(4, 6);
觀察者模式
interface Observer {
update(data: any): void;
}
class Subject {
private observers: Observer[] = [];
subscribe(observer: Observer): void {
this.observers.push(observer);
}
unsubscribe(observer: Observer): void {
this.observers = this.observers.filter((o) => o !== observer);
}
notify(data: any): void {
this.observers.forEach((observer) => observer.update(data));
}
}
class ConcreteObserver implements Observer {
constructor(private name: string) {}
update(data: any): void {
console.log(`${this.name} received: ${data}`);
}
}
const subject = new Subject();
const observer1 = new ConcreteObserver('Observer 1');
const observer2 = new ConcreteObserver('Observer 2');
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify('Hello!');
// Observer 1 received: Hello!
// Observer 2 received: Hello!