チュートリアル

TypeScript基礎:継承とインターフェース

TypeScript入門オブジェクト指向インターフェース
広告エリア

クラスの継承

extends

class Animal {
  constructor(public name: string) {}

  speak(): void {
    console.log(`${this.name}が鳴いています`);
  }
}

class Dog extends Animal {
  constructor(name: string, public breed: string) {
    super(name);  // 親のコンストラクタ呼び出し
  }

  // オーバーライド
  speak(): void {
    console.log(`${this.name}がワンワン鳴いています`);
  }

  // 新しいメソッド
  fetch(): void {
    console.log(`${this.name}がボールを取ってきます`);
  }
}

const dog = new Dog("ポチ", "柴犬");
dog.speak();  // "ポチがワンワン鳴いています"
dog.fetch();  // "ポチがボールを取ってきます"

super キーワード

class Vehicle {
  constructor(public speed: number) {}

  describe(): string {
    return `速度: ${this.speed}km/h`;
  }
}

class Car extends Vehicle {
  constructor(speed: number, public brand: string) {
    super(speed);
  }

  // 親のメソッドを呼び出して拡張
  describe(): string {
    return `${this.brand} - ${super.describe()}`;
  }
}

const car = new Car(120, "トヨタ");
console.log(car.describe());  // "トヨタ - 速度: 120km/h"

インターフェース

基本的なインターフェース

interface User {
  id: number;
  name: string;
  email: string;
  age?: number;  // オプショナル
  readonly createdAt: Date;  // 読み取り専用
}

const user: User = {
  id: 1,
  name: "太郎",
  email: "taro@example.com",
  createdAt: new Date()
};

インターフェースの拡張

interface Person {
  name: string;
  age: number;
}

interface Employee extends Person {
  employeeId: number;
  department: string;
}

// 複数のインターフェースを拡張
interface Manager extends Employee {
  subordinates: Employee[];
}

const manager: Manager = {
  name: "太郎",
  age: 40,
  employeeId: 1,
  department: "開発部",
  subordinates: []
};

クラスへの実装

interface Printable {
  print(): void;
}

interface Serializable {
  serialize(): string;
}

// 複数のインターフェースを実装
class Document implements Printable, Serializable {
  constructor(public title: string, public content: string) {}

  print(): void {
    console.log(`${this.title}\n${this.content}`);
  }

  serialize(): string {
    return JSON.stringify({ title: this.title, content: this.content });
  }
}

関数型インターフェース

interface MathOperation {
  (a: number, b: number): number;
}

const add: MathOperation = (a, b) => a + b;
const multiply: MathOperation = (a, b) => a * b;

// コールシグネチャとプロパティの両方
interface Counter {
  (start: number): number;
  interval: number;
  reset(): void;
}

ジェネリクス

ジェネリッククラス

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 stringContainer = new Container<string>();
stringContainer.add("hello");
stringContainer.add("world");

const numberContainer = new Container<number>();
numberContainer.add(1);
numberContainer.add(2);

ジェネリックインターフェース

interface Repository<T> {
  findById(id: number): T | null;
  findAll(): T[];
  save(item: T): void;
  delete(id: number): boolean;
}

interface User {
  id: number;
  name: string;
}

class UserRepository implements Repository<User> {
  private users: User[] = [];

  findById(id: number): User | null {
    return this.users.find(u => u.id === id) || null;
  }

  findAll(): User[] {
    return [...this.users];
  }

  save(user: User): void {
    this.users.push(user);
  }

  delete(id: number): boolean {
    const index = this.users.findIndex(u => u.id === id);
    if (index >= 0) {
      this.users.splice(index, 1);
      return true;
    }
    return false;
  }
}

制約付きジェネリクス

interface HasId {
  id: number;
}

// T は id プロパティを持つ必要がある
function findById<T extends HasId>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id);
}

interface User extends HasId {
  name: string;
}

interface Product extends HasId {
  title: string;
  price: number;
}

const users: User[] = [{ id: 1, name: "太郎" }];
const products: Product[] = [{ id: 1, title: "本", price: 1500 }];

findById(users, 1);     // User | undefined
findById(products, 1);  // Product | undefined

ユーティリティ型

Partial と Required

interface User {
  id: number;
  name: string;
  email: string;
}

// すべてのプロパティをオプショナルに
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; }

// すべてのプロパティを必須に
interface Config {
  debug?: boolean;
  timeout?: number;
}
type RequiredConfig = Required<Config>;
// { debug: boolean; timeout: number; }

Pick と Omit

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

// 特定のプロパティだけ選択
type PublicUser = Pick<User, "id" | "name" | "email">;
// { id: number; name: string; email: string; }

// 特定のプロパティを除外
type UserWithoutPassword = Omit<User, "password">;
// { id: number; name: string; email: string; }

Record

// キーと値の型を指定したオブジェクト型
type UserRoles = Record<string, string[]>;

const roles: UserRoles = {
  admin: ["read", "write", "delete"],
  user: ["read"],
  guest: []
};

// リテラル型と組み合わせ
type Status = "pending" | "approved" | "rejected";
type StatusMessages = Record<Status, string>;

const messages: StatusMessages = {
  pending: "審査中",
  approved: "承認済み",
  rejected: "却下"
};

Readonly と ReadonlyArray

interface User {
  id: number;
  name: string;
}

type ReadonlyUser = Readonly<User>;
// { readonly id: number; readonly name: string; }

const user: ReadonlyUser = { id: 1, name: "太郎" };
// user.name = "花子";  // エラー

// 読み取り専用配列
const numbers: ReadonlyArray<number> = [1, 2, 3];
// numbers.push(4);  // エラー

ポリモーフィズム

abstract class Shape {
  abstract area(): number;
}

class Rectangle extends Shape {
  constructor(public width: number, public height: number) {
    super();
  }

  area(): number {
    return this.width * this.height;
  }
}

class Circle extends Shape {
  constructor(public radius: number) {
    super();
  }

  area(): number {
    return Math.PI * this.radius ** 2;
  }
}

// 型安全なポリモーフィズム
function printArea(shape: Shape): void {
  console.log(`面積: ${shape.area()}`);
}

printArea(new Rectangle(10, 5));  // "面積: 50"
printArea(new Circle(7));         // "面積: 153.93..."

実践例:DIコンテナ

interface Logger {
  log(message: string): void;
}

interface Database {
  query(sql: string): unknown[];
}

class ConsoleLogger implements Logger {
  log(message: string): void {
    console.log(`[LOG] ${message}`);
  }
}

class PostgresDatabase implements Database {
  query(sql: string): unknown[] {
    console.log(`Executing: ${sql}`);
    return [];
  }
}

// DIコンテナ
class Container {
  private services = new Map<string, unknown>();

  register<T>(key: string, service: T): void {
    this.services.set(key, service);
  }

  resolve<T>(key: string): T {
    const service = this.services.get(key);
    if (!service) {
      throw new Error(`Service not found: ${key}`);
    }
    return service as T;
  }
}

// 使用例
const container = new Container();
container.register<Logger>("logger", new ConsoleLogger());
container.register<Database>("db", new PostgresDatabase());

class UserService {
  constructor(
    private logger: Logger,
    private db: Database
  ) {}

  getUsers(): void {
    this.logger.log("Fetching users");
    this.db.query("SELECT * FROM users");
  }
}

const userService = new UserService(
  container.resolve<Logger>("logger"),
  container.resolve<Database>("db")
);
userService.getUsers();

まとめ

  • extendsでクラスを継承
  • implementsでインターフェースを実装
  • インターフェースで契約を定義
  • ジェネリクスで再利用可能な型を作成
  • extends制約でジェネリクスに制限
  • Partial, Pick, Omitなどのユーティリティ型
  • 抽象クラスとインターフェースでポリモーフィズム

これでTypeScript基礎は完了です。次は実践編でファイル操作やエラー処理を学びましょう。

広告エリア