Day 8: クラス
今日学ぶこと
- TypeScriptにおけるクラスの基本
- アクセス修飾子(public, private, protected)
- readonlyプロパティ
- 抽象クラス
- インターフェースの実装(implements)
クラスの基本
TypeScriptのクラスは、JavaScriptのクラスに型の安全性を追加したものです。
class User {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): string {
return `Hello, I'm ${this.name}!`;
}
}
const user = new User("Alice", 25);
console.log(user.greet()); // "Hello, I'm Alice!"
classDiagram
class User {
+string name
+number age
+constructor(name, age)
+greet() string
}
プロパティの初期化
TypeScriptでは、プロパティは宣言時またはコンストラクタ内で初期化する必要があります。
class Product {
// 宣言時に初期化
category: string = "general";
// コンストラクタで初期化
name: string;
price: number;
constructor(name: string, price: number) {
this.name = name;
this.price = price;
}
}
アクセス修飾子
TypeScriptには3つのアクセス修飾子があります。
public(デフォルト)
どこからでもアクセス可能です。
class User {
public name: string; // public は省略可能
constructor(name: string) {
this.name = name;
}
}
const user = new User("Alice");
console.log(user.name); // OK
private
クラス内からのみアクセス可能です。
class BankAccount {
private balance: number;
constructor(initialBalance: number) {
this.balance = initialBalance;
}
deposit(amount: number): void {
this.balance += amount;
}
getBalance(): number {
return this.balance;
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
console.log(account.balance); // エラー: privateプロパティ
protected
クラス内と継承したクラスからアクセス可能です。
class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
bark(): void {
// protectedなのでアクセス可能
console.log(`${this.name} says: Woof!`);
}
}
const dog = new Dog("Max");
dog.bark(); // OK: "Max says: Woof!"
dog.name; // エラー: protectedプロパティ
flowchart TB
subgraph Access["アクセス可能な場所"]
direction LR
Public["public\nどこでも"]
Protected["protected\nクラス内 + 継承"]
Private["private\nクラス内のみ"]
end
style Public fill:#22c55e,color:#fff
style Protected fill:#f59e0b,color:#fff
style Private fill:#ef4444,color:#fff
パラメータプロパティ
コンストラクタの引数に修飾子をつけると、プロパティ宣言と代入を省略できます。
// 通常の書き方
class User {
private name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
// パラメータプロパティを使用
class User2 {
constructor(
private name: string,
private age: number
) {}
// 自動的にthis.name, this.ageが設定される
}
readonly修飾子
readonlyをつけると、初期化後に変更できなくなります。
class User {
readonly id: string;
name: string;
constructor(id: string, name: string) {
this.id = id; // コンストラクタ内でのみ代入可能
this.name = name;
}
changeName(newName: string): void {
this.name = newName; // OK
}
changeId(newId: string): void {
this.id = newId; // エラー: readonlyプロパティ
}
}
クラスの継承
extendsキーワードで既存のクラスを継承できます。
class Animal {
constructor(protected name: string) {}
move(distance: number): void {
console.log(`${this.name} moved ${distance}m`);
}
}
class Dog extends Animal {
constructor(name: string, private breed: string) {
super(name); // 親クラスのコンストラクタを呼び出し
}
bark(): void {
console.log(`${this.name} says: Woof!`);
}
// メソッドのオーバーライド
move(distance: number): void {
console.log(`${this.name} runs...`);
super.move(distance); // 親のメソッドを呼び出し
}
}
const dog = new Dog("Max", "Labrador");
dog.bark(); // "Max says: Woof!"
dog.move(10); // "Max runs..." → "Max moved 10m"
classDiagram
Animal <|-- Dog
class Animal {
#string name
+move(distance)
}
class Dog {
-string breed
+bark()
+move(distance)
}
抽象クラス
抽象クラスは、直接インスタンス化できないクラスです。継承して使用します。
abstract class Shape {
abstract getArea(): number; // 抽象メソッド:実装は子クラスで
printArea(): void {
console.log(`Area: ${this.getArea()}`);
}
}
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;
}
}
// const shape = new Shape(); // エラー: 抽象クラスはインスタンス化不可
const circle = new Circle(5);
circle.printArea(); // "Area: 78.53981633974483"
classDiagram
Shape <|-- Circle
Shape <|-- Rectangle
class Shape {
<<abstract>>
+getArea()* number
+printArea()
}
class Circle {
-number radius
+getArea() number
}
class Rectangle {
-number width
-number height
+getArea() number
}
インターフェースの実装
クラスはインターフェースをimplementsで実装できます。
interface Printable {
print(): void;
}
interface Serializable {
serialize(): string;
}
// 複数のインターフェースを実装
class Document implements Printable, Serializable {
constructor(private content: string) {}
print(): void {
console.log(this.content);
}
serialize(): string {
return JSON.stringify({ content: this.content });
}
}
const doc = new Document("Hello, World!");
doc.print(); // "Hello, World!"
doc.serialize(); // '{"content":"Hello, World!"}'
インターフェース vs 抽象クラス
| 特徴 | interface | abstract class |
|---|---|---|
| 多重実装/継承 | ✅ 複数可 | ❌ 単一のみ |
| デフォルト実装 | ❌ | ✅ |
| プロパティの実装 | ❌ | ✅ |
| コンストラクタ | ❌ | ✅ |
// インターフェース:契約を定義
interface Logger {
log(message: string): void;
}
// 抽象クラス:共通の実装を提供
abstract class BaseLogger implements Logger {
abstract log(message: string): void;
// 共通のメソッド
error(message: string): void {
this.log(`[ERROR] ${message}`);
}
}
class ConsoleLogger extends BaseLogger {
log(message: string): void {
console.log(message);
}
}
クラスと型
TypeScriptでは、クラスは値であり型でもあります。
class User {
constructor(public name: string) {}
}
// User は値(コンストラクタ関数)
const user = new User("Alice");
// User は型としても使える
function greet(user: User): void {
console.log(`Hello, ${user.name}!`);
}
// インターフェースと互換(構造的部分型)
interface HasName {
name: string;
}
const obj: HasName = { name: "Bob" };
greet(obj); // OK: 構造が一致
まとめ
| 概念 | 説明 | 例 |
|---|---|---|
| public | どこからでもアクセス | public name: string |
| private | クラス内のみ | private balance: number |
| protected | クラス内 + 継承先 | protected id: string |
| readonly | 初期化後は変更不可 | readonly createdAt: Date |
| abstract | 抽象クラス/メソッド | abstract class Shape |
| implements | インターフェースを実装 | class Dog implements Animal |
重要ポイント
- アクセス修飾子で隠蔽 - 内部実装を隠す
- パラメータプロパティで簡潔に - ボイラープレートを削減
- 抽象クラスで共通実装 - 継承で再利用
- interfaceで契約定義 - 実装の詳細は隠す
練習問題
問題1: 基本
以下の要件を満たすCounterクラスを作成してください。
countプロパティ(privateでreadonly不可)increment()メソッドdecrement()メソッドgetCount()メソッド
問題2: 継承
Animalクラスを継承してBirdクラスを作成してください。
Animalはnameとmove()を持つBirdはfly()メソッドを追加し、move()をオーバーライド
チャレンジ問題
以下の要件を満たすシステムを設計してください。
Storageインターフェース:save(),load(),delete()MemoryStorageとFileStorageクラスで実装- 共通の
BaseStorage抽象クラスを作成し、エラーハンドリングを共通化
参考リンク
次回予告: Day 9では「ジェネリクス」を学びます。型をパラメータ化して、再利用可能な型定義を作成しましょう。