チュートリアル

JavaScript基礎:継承とオブジェクト指向

JavaScript入門オブジェクト指向継承
広告エリア

継承(extends)

extendsキーワードで既存クラスを拡張します。

// 親クラス(スーパークラス)
class Animal {
  constructor(name) {
    this.name = name;
  }

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

  move() {
    console.log(`${this.name}が移動しています`);
  }
}

// 子クラス(サブクラス)
class Dog extends Animal {
  constructor(name, breed) {
    super(name);  // 親のコンストラクタを呼び出し
    this.breed = breed;
  }

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

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

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

super キーワード

親コンストラクタの呼び出し

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

class Employee extends Person {
  constructor(name, age, department) {
    // 子のコンストラクタではsuper()を最初に呼ぶ必要がある
    super(name, age);
    this.department = department;
  }
}

const employee = new Employee("太郎", 30, "開発部");

親メソッドの呼び出し

class Animal {
  speak() {
    return "...";
  }
}

class Cat extends Animal {
  speak() {
    // 親のメソッドを呼び出して拡張
    const base = super.speak();
    return `${base} ニャー`;
  }
}

const cat = new Cat();
console.log(cat.speak());  // "... ニャー"

ポリモーフィズム

同じインターフェースで異なる動作を実現します。

class Shape {
  area() {
    throw new Error("サブクラスで実装してください");
  }

  describe() {
    return `面積: ${this.area()}`;
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

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

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

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

// 同じメソッドで異なる動作
const shapes = [
  new Rectangle(10, 5),
  new Circle(7),
  new Rectangle(3, 4)
];

shapes.forEach(shape => {
  console.log(shape.describe());
});
// "面積: 50"
// "面積: 153.93..."
// "面積: 12"

静的メソッドの継承

class Animal {
  static create(name) {
    return new this(name);
  }
}

class Dog extends Animal {
  constructor(name) {
    super();
    this.name = name;
  }
}

// 静的メソッドも継承される
const dog = Dog.create("ポチ");
console.log(dog instanceof Dog);  // true

ミックスイン

JavaScriptは単一継承ですが、ミックスインで複数の機能を組み合わせられます。

// ミックスイン関数
const canFly = {
  fly() {
    console.log(`${this.name}が飛んでいます`);
  }
};

const canSwim = {
  swim() {
    console.log(`${this.name}が泳いでいます`);
  }
};

class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Duck extends Animal {
  constructor(name) {
    super(name);
  }
}

// ミックスインを適用
Object.assign(Duck.prototype, canFly, canSwim);

const duck = new Duck("ドナルド");
duck.fly();   // "ドナルドが飛んでいます"
duck.swim();  // "ドナルドが泳いでいます"

ミックスイン関数パターン

function withLogging(Base) {
  return class extends Base {
    log(message) {
      console.log(`[${new Date().toISOString()}] ${message}`);
    }
  };
}

function withTimestamp(Base) {
  return class extends Base {
    constructor(...args) {
      super(...args);
      this.createdAt = new Date();
    }
  };
}

class User {
  constructor(name) {
    this.name = name;
  }
}

// ミックスインを適用
const EnhancedUser = withTimestamp(withLogging(User));

const user = new EnhancedUser("太郎");
user.log("ログイン");
console.log(user.createdAt);

抽象クラスのシミュレーション

JavaScriptには抽象クラスがありませんが、シミュレートできます。

class AbstractShape {
  constructor() {
    if (new.target === AbstractShape) {
      throw new Error("AbstractShapeは直接インスタンス化できません");
    }
  }

  // 抽象メソッド
  area() {
    throw new Error("area()を実装してください");
  }

  // 具象メソッド
  describe() {
    return `この図形の面積は${this.area()}です`;
  }
}

class Square extends AbstractShape {
  constructor(side) {
    super();
    this.side = side;
  }

  area() {
    return this.side ** 2;
  }
}

// const shape = new AbstractShape();  // エラー
const square = new Square(5);
console.log(square.describe());  // "この図形の面積は25です"

カプセル化

class Person {
  #name;
  #age;

  constructor(name, age) {
    this.#name = name;
    this.#age = age;
  }

  get name() {
    return this.#name;
  }

  get age() {
    return this.#age;
  }

  set age(value) {
    if (value < 0) {
      throw new Error("年齢は0以上である必要があります");
    }
    this.#age = value;
  }

  birthday() {
    this.#age++;
  }
}

const person = new Person("太郎", 25);
console.log(person.name);  // "太郎"
person.birthday();
console.log(person.age);   // 26
// person.age = -1;  // エラー

コンポジション(委譲)

継承よりもコンポジションが適切な場合があります。

// 継承より委譲が適切な例
class Engine {
  start() {
    console.log("エンジン始動");
  }

  stop() {
    console.log("エンジン停止");
  }
}

class Wheels {
  rotate() {
    console.log("タイヤ回転");
  }
}

// 継承ではなくコンポジション
class Car {
  constructor() {
    this.engine = new Engine();
    this.wheels = new Wheels();
  }

  drive() {
    this.engine.start();
    this.wheels.rotate();
    console.log("走行中...");
  }

  park() {
    this.engine.stop();
    console.log("駐車完了");
  }
}

const car = new Car();
car.drive();
car.park();

実践例:通知システム

// 基底クラス
class Notification {
  constructor(message) {
    this.message = message;
    this.timestamp = new Date();
  }

  send() {
    throw new Error("サブクラスでsend()を実装してください");
  }

  format() {
    return `[${this.timestamp.toLocaleString()}] ${this.message}`;
  }
}

// メール通知
class EmailNotification extends Notification {
  constructor(message, to) {
    super(message);
    this.to = to;
  }

  send() {
    console.log(`メール送信先: ${this.to}`);
    console.log(`内容: ${this.format()}`);
    return true;
  }
}

// SMS通知
class SMSNotification extends Notification {
  constructor(message, phoneNumber) {
    super(message);
    this.phoneNumber = phoneNumber;
  }

  send() {
    const shortMessage = this.message.substring(0, 160);
    console.log(`SMS送信先: ${this.phoneNumber}`);
    console.log(`内容: ${shortMessage}`);
    return true;
  }
}

// プッシュ通知
class PushNotification extends Notification {
  constructor(message, deviceToken) {
    super(message);
    this.deviceToken = deviceToken;
  }

  send() {
    console.log(`プッシュ通知: ${this.deviceToken}`);
    console.log(`内容: ${this.message}`);
    return true;
  }
}

// 通知マネージャー
class NotificationManager {
  #notifications = [];

  add(notification) {
    this.#notifications.push(notification);
  }

  sendAll() {
    for (const notification of this.#notifications) {
      notification.send();  // ポリモーフィズム
      console.log("---");
    }
    this.#notifications = [];
  }
}

// 使用例
const manager = new NotificationManager();
manager.add(new EmailNotification("会議の招待", "user@example.com"));
manager.add(new SMSNotification("認証コード: 123456", "090-1234-5678"));
manager.add(new PushNotification("新着メッセージがあります", "device-token-xyz"));

manager.sendAll();

まとめ

  • extendsでクラスを継承
  • superで親クラスにアクセス
  • ポリモーフィズムで柔軟な設計
  • ミックスインで機能を合成
  • プライベートプロパティでカプセル化
  • 継承よりコンポジションを検討

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

広告エリア