継承(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基礎は完了です。次は実践編でファイル操作やエラー処理を学びましょう。