JavaScript ES6のクラス構文とアクセス指定子の完全ガイド

JavaScriptのES6で導入されたクラス構文は、オブジェクト指向プログラミングをよりシンプルかつ直感的に行うための重要な機能です。クラス構文により、従来のプロトタイプベースの継承よりも分かりやすい形でクラスの定義や継承が可能となりました。また、アクセス指定子を用いることで、クラス内のメンバ変数やメソッドへのアクセス制御が可能になり、より堅牢なコード設計が可能です。本記事では、ES6クラス構文の基本からアクセス指定子の詳細、実際の利用例までを包括的に解説します。JavaScript開発者にとって不可欠な知識を深め、より効果的なコーディングができるようになることを目指します。

目次

ES6クラス構文の基本

ES6で導入されたクラス構文は、JavaScriptにおけるオブジェクト指向プログラミングをサポートするためのものです。従来のプロトタイプベースの継承に比べて、クラス構文はよりシンプルで直感的です。

クラスの定義

クラスはclassキーワードを使って定義します。以下は基本的なクラス定義の例です。

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

    greet() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
}

この例では、Personというクラスを定義しています。コンストラクタは、インスタンスが作成されるときに呼び出され、nameageのプロパティを初期化します。greetメソッドは、インスタンスの名前と年齢を表示します。

クラスのインスタンス化

クラスをインスタンス化するには、newキーワードを使用します。

const person1 = new Person('Alice', 30);
person1.greet(); // Hello, my name is Alice and I am 30 years old.

ここで、person1Personクラスのインスタンスであり、greetメソッドを呼び出すことができます。

クラスの継承

クラスはextendsキーワードを使って他のクラスを継承することができます。

class Employee extends Person {
    constructor(name, age, jobTitle) {
        super(name, age);
        this.jobTitle = jobTitle;
    }

    describeJob() {
        console.log(`I am a ${this.jobTitle}.`);
    }
}

この例では、EmployeeクラスがPersonクラスを継承しています。superキーワードを使って親クラスのコンストラクタを呼び出し、追加のプロパティjobTitleを初期化しています。

const employee1 = new Employee('Bob', 25, 'Developer');
employee1.greet(); // Hello, my name is Bob and I am 25 years old.
employee1.describeJob(); // I am a Developer.

以上がES6クラス構文の基本です。次に、アクセス指定子の概念について説明します。

アクセス指定子とは

アクセス指定子は、クラス内のプロパティやメソッドへのアクセスレベルを制御するためのものです。JavaScriptでは、ES6のクラス構文と組み合わせて使うことで、クラスの設計をより安全かつ明確にすることができます。アクセス指定子には主にパブリック、プライベート、プロテクテッドの3種類があります。

パブリックアクセス指定子

パブリックアクセス指定子は、クラスの外部からでもアクセス可能なプロパティやメソッドを示します。JavaScriptでは、デフォルトで全てのプロパティやメソッドはパブリックです。

class Car {
    constructor(brand, model) {
        this.brand = brand;
        this.model = model;
    }

    getDetails() {
        return `${this.brand} ${this.model}`;
    }
}

const myCar = new Car('Toyota', 'Corolla');
console.log(myCar.brand); // Toyota
console.log(myCar.getDetails()); // Toyota Corolla

上記の例では、brandmodelプロパティ、getDetailsメソッドはすべてパブリックです。

プライベートアクセス指定子

プライベートアクセス指定子は、クラスの外部からアクセスできないプロパティやメソッドを示します。JavaScriptでは、プライベートプロパティやメソッドを定義するために、名前の先頭に#を付けます。

class Car {
    #brand;
    #model;

    constructor(brand, model) {
        this.#brand = brand;
        this.#model = model;
    }

    #getDetails() {
        return `${this.#brand} ${this.#model}`;
    }

    showDetails() {
        console.log(this.#getDetails());
    }
}

const myCar = new Car('Toyota', 'Corolla');
// console.log(myCar.#brand); // エラー: プライベートプロパティにはアクセスできません
myCar.showDetails(); // Toyota Corolla

この例では、#brand#modelプロパティ、#getDetailsメソッドはプライベートであり、クラスの外部から直接アクセスすることはできません。

プロテクテッドアクセス指定子(疑似的な実装)

JavaScriptではネイティブにプロテクテッドアクセス指定子はサポートされていませんが、命名規則を利用して疑似的に実装することができます。プロテクテッドは、同じクラスまたはサブクラスからのみアクセス可能なプロパティやメソッドを示します。

class Car {
    _brand;
    _model;

    constructor(brand, model) {
        this._brand = brand;
        this._model = model;
    }

    _getDetails() {
        return `${this._brand} ${this._model}`;
    }

    showDetails() {
        console.log(this._getDetails());
    }
}

class ElectricCar extends Car {
    constructor(brand, model, battery) {
        super(brand, model);
        this.battery = battery;
    }

    showBattery() {
        console.log(`${this._brand} ${this._model} has a ${this.battery} battery`);
    }
}

const myElectricCar = new ElectricCar('Tesla', 'Model S', '100 kWh');
myElectricCar.showBattery(); // Tesla Model S has a 100 kWh battery

この例では、_brand_modelプロパティ、_getDetailsメソッドは命名規則としてプロテクテッドと見なされ、同じクラスまたはそのサブクラスからのみアクセスされることを意図しています。

次のセクションでは、各アクセス指定子の具体的な使い方と例を詳しく見ていきます。

パブリックアクセス指定子

パブリックアクセス指定子は、クラスの外部からでもアクセス可能なプロパティやメソッドを示します。JavaScriptでは、デフォルトで全てのプロパティやメソッドはパブリックと見なされます。

パブリックプロパティの定義

パブリックプロパティは、クラスのインスタンスから直接アクセスおよび変更が可能です。

class Book {
    constructor(title, author) {
        this.title = title;
        this.author = author;
    }

    getDetails() {
        return `${this.title} by ${this.author}`;
    }
}

const myBook = new Book('1984', 'George Orwell');
console.log(myBook.title); // 1984
console.log(myBook.author); // George Orwell
myBook.title = 'Animal Farm';
console.log(myBook.getDetails()); // Animal Farm by George Orwell

この例では、titleauthorはパブリックプロパティであり、クラスの外部からアクセスして変更することができます。

パブリックメソッドの定義

パブリックメソッドは、クラスのインスタンスから呼び出すことができます。

class Calculator {
    add(a, b) {
        return a + b;
    }

    subtract(a, b) {
        return a - b;
    }
}

const calculator = new Calculator();
console.log(calculator.add(5, 3)); // 8
console.log(calculator.subtract(5, 3)); // 2

この例では、addsubtractはパブリックメソッドであり、クラスの外部から呼び出すことができます。

パブリックアクセス指定子の利点

パブリックアクセス指定子は、クラスの外部からプロパティやメソッドにアクセスする必要がある場合に非常に便利です。例えば、ユーザーインターフェースからデータを取得したり設定したりする場合や、他のクラスと連携して動作する必要がある場合に役立ちます。

パブリックプロパティとメソッドを適切に利用することで、クラスの使いやすさと柔軟性を向上させることができます。しかし、必要以上にパブリックにしすぎると、クラスの内部状態を外部から不適切に変更されるリスクがあるため、注意が必要です。

次のセクションでは、プライベートアクセス指定子の使い方とその制限について説明します。

プライベートアクセス指定子

プライベートアクセス指定子は、クラスの外部からアクセスできないプロパティやメソッドを示します。JavaScriptでは、プライベートプロパティやメソッドを定義するために、名前の先頭に#を付けます。このアクセス指定子を使用することで、クラスの内部状態を保護し、不正なアクセスや変更を防ぐことができます。

プライベートプロパティの定義

プライベートプロパティは、クラスの内部からのみアクセス可能です。これにより、データのカプセル化が強化されます。

class BankAccount {
    #balance;

    constructor(initialBalance) {
        this.#balance = initialBalance;
    }

    deposit(amount) {
        this.#balance += amount;
    }

    withdraw(amount) {
        if (amount <= this.#balance) {
            this.#balance -= amount;
        } else {
            console.log('Insufficient funds');
        }
    }

    getBalance() {
        return this.#balance;
    }
}

const myAccount = new BankAccount(1000);
// console.log(myAccount.#balance); // エラー: プライベートプロパティにはアクセスできません
myAccount.deposit(500);
console.log(myAccount.getBalance()); // 1500
myAccount.withdraw(200);
console.log(myAccount.getBalance()); // 1300
myAccount.withdraw(2000); // Insufficient funds

この例では、#balanceはプライベートプロパティであり、クラスの外部から直接アクセスすることはできません。depositwithdrawメソッドを通じてのみ操作可能です。

プライベートメソッドの定義

プライベートメソッドは、クラスの内部からのみ呼び出すことができます。これにより、クラスの内部ロジックを隠蔽することができます。

class Safe {
    #combination;

    constructor(combination) {
        this.#combination = combination;
    }

    #isCorrectCombination(input) {
        return input === this.#combination;
    }

    open(input) {
        if (this.#isCorrectCombination(input)) {
            console.log('Safe opened');
        } else {
            console.log('Incorrect combination');
        }
    }
}

const mySafe = new Safe('1234');
mySafe.open('1234'); // Safe opened
mySafe.open('0000'); // Incorrect combination
// console.log(mySafe.#isCorrectCombination('1234')); // エラー: プライベートメソッドにはアクセスできません

この例では、#isCorrectCombinationはプライベートメソッドであり、クラスの内部からのみ呼び出すことができます。

プライベートアクセス指定子の利点

プライベートアクセス指定子を使用することで、以下の利点があります。

  • データのカプセル化:クラスの内部状態を外部から隠蔽することで、データの一貫性を保つことができます。
  • 安全性の向上:プライベートプロパティやメソッドを使用することで、不正なアクセスや変更を防ぐことができます。
  • メンテナンスの容易さ:内部実装の詳細を隠すことで、クラスの外部に影響を与えることなく内部の変更を行いやすくなります。

次のセクションでは、JavaScriptにおけるプロテクテッドアクセス指定子の疑似的な実装方法について説明します。

プロテクテッドアクセス指定子(疑似的な実装)

JavaScriptではネイティブにプロテクテッドアクセス指定子はサポートされていません。しかし、命名規則や特定の方法を用いることで、プロテクテッドの概念を疑似的に実装することができます。プロテクテッドは、同じクラスまたはサブクラスからのみアクセス可能なプロパティやメソッドを示します。

プロテクテッドの命名規則

命名規則として、プロテクテッドと見なすプロパティやメソッドの名前をアンダースコア(_)で始める方法があります。この命名規則により、開発者に対してこれらのメンバは内部的なものであり、直接アクセスすべきではないことを示すことができます。

class Device {
    _status;

    constructor(status) {
        this._status = status;
    }

    _getStatus() {
        return this._status;
    }

    showStatus() {
        console.log(this._getStatus());
    }
}

class Smartphone extends Device {
    constructor(status, model) {
        super(status);
        this.model = model;
    }

    displayInfo() {
        console.log(`Model: ${this.model}, Status: ${this._getStatus()}`);
    }
}

const myPhone = new Smartphone('active', 'iPhone 12');
myPhone.displayInfo(); // Model: iPhone 12, Status: active

この例では、_statusプロパティと_getStatusメソッドはプロテクテッドと見なされ、DeviceクラスとそのサブクラスSmartphoneからのみアクセスされています。

Symbolを用いたプロテクテッドの実装

もう一つの方法として、Symbolを用いてプロテクテッドに近い実装を行うことができます。Symbolは一意の識別子を生成するため、プロパティの名前が衝突することを防ぎます。

const _status = Symbol('status');

class Device {
    constructor(status) {
        this[_status] = status;
    }

    _getStatus() {
        return this[_status];
    }

    showStatus() {
        console.log(this._getStatus());
    }
}

class Smartphone extends Device {
    constructor(status, model) {
        super(status);
        this.model = model;
    }

    displayInfo() {
        console.log(`Model: ${this.model}, Status: ${this._getStatus()}`);
    }
}

const myPhone = new Smartphone('active', 'iPhone 12');
myPhone.displayInfo(); // Model: iPhone 12, Status: active

この例では、_statusSymbolを用いて定義されており、同じクラスまたはサブクラス内でのみアクセスされることを意図しています。

プロテクテッドアクセス指定子の利点

プロテクテッドアクセス指定子を疑似的に実装することで、以下の利点があります。

  • 継承関係の保護:クラス階層内でのみアクセス可能なプロパティやメソッドを定義することで、サブクラスが親クラスの重要なデータやメソッドにアクセスできるようにします。
  • コードの読みやすさ:命名規則を使用することで、どのメンバが内部的なものであり、外部からアクセスすべきではないかを明示的に示すことができます。

次のセクションでは、アクセス指定子の実践例を詳しく見ていきます。実際のコードを用いて、各アクセス指定子の利用方法を具体的に説明します。

アクセス指定子の実践例

アクセス指定子を使用することで、クラスの設計をより堅牢にし、内部データの保護やコードの可読性を向上させることができます。このセクションでは、パブリック、プライベート、プロテクテッドアクセス指定子を用いた実践的な例を紹介します。

パブリックアクセス指定子の例

パブリックプロパティとメソッドは、クラスの外部から直接アクセスできるため、ユーザーインターフェースや他のクラスと連携するために使用されます。

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

    displayInfo() {
        console.log(`Name: ${this.name}, Email: ${this.email}`);
    }
}

const user = new User('John Doe', 'john.doe@example.com');
user.displayInfo(); // Name: John Doe, Email: john.doe@example.com
user.name = 'Jane Doe';
user.displayInfo(); // Name: Jane Doe, Email: john.doe@example.com

この例では、nameemailプロパティ、displayInfoメソッドはパブリックであり、クラスの外部から自由にアクセスできます。

プライベートアクセス指定子の例

プライベートプロパティとメソッドは、クラスの内部からのみアクセス可能です。これにより、データのカプセル化とセキュリティが強化されます。

class BankAccount {
    #balance;

    constructor(initialBalance) {
        this.#balance = initialBalance;
    }

    deposit(amount) {
        if (amount > 0) {
            this.#balance += amount;
        }
    }

    withdraw(amount) {
        if (amount > 0 && amount <= this.#balance) {
            this.#balance -= amount;
        }
    }

    getBalance() {
        return this.#balance;
    }
}

const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
account.withdraw(200);
console.log(account.getBalance()); // 1300
// console.log(account.#balance); // エラー: プライベートプロパティにはアクセスできません

この例では、#balanceはプライベートプロパティであり、クラスの外部から直接アクセスすることはできません。depositwithdrawメソッドを通じてのみ操作可能です。

プロテクテッドアクセス指定子の疑似実装例

プロテクテッドプロパティとメソッドは、クラスの内部およびそのサブクラスからのみアクセス可能です。JavaScriptでは、命名規則を用いてこれを実現します。

class Person {
    _name;

    constructor(name) {
        this._name = name;
    }

    _getName() {
        return this._name;
    }

    introduce() {
        console.log(`Hello, my name is ${this._getName()}`);
    }
}

class Employee extends Person {
    constructor(name, jobTitle) {
        super(name);
        this.jobTitle = jobTitle;
    }

    introduce() {
        console.log(`Hello, my name is ${this._getName()} and I am a ${this.jobTitle}`);
    }
}

const employee = new Employee('Alice', 'Engineer');
employee.introduce(); // Hello, my name is Alice and I am a Engineer

この例では、_nameプロパティと_getNameメソッドはプロテクテッドと見なされ、PersonクラスおよびそのサブクラスEmployeeからのみアクセスされます。

アクセス指定子を組み合わせた実践例

アクセス指定子を組み合わせることで、クラスの設計をより柔軟かつ安全にすることができます。

class Product {
    #id;
    _price;

    constructor(id, name, price) {
        this.#id = id;
        this.name = name;
        this._price = price;
    }

    getDetails() {
        return `Product ID: ${this.#id}, Name: ${this.name}, Price: ${this._price}`;
    }

    setPrice(newPrice) {
        if (newPrice > 0) {
            this._price = newPrice;
        }
    }
}

class DiscountedProduct extends Product {
    constructor(id, name, price, discount) {
        super(id, name, price);
        this.discount = discount;
    }

    applyDiscount() {
        this.setPrice(this._price - this.discount);
    }
}

const product = new DiscountedProduct(1, 'Laptop', 1000, 100);
product.applyDiscount();
console.log(product.getDetails()); // Product ID: 1, Name: Laptop, Price: 900

この例では、#idはプライベートプロパティ、_priceはプロテクテッドプロパティとして定義されており、それぞれ異なるアクセスレベルで保護されています。

次のセクションでは、アクセス指定子と継承の関係について詳しく説明します。継承関係におけるアクセス指定子の役割と影響を解説します。

アクセス指定子と継承

アクセス指定子は、クラスの継承関係において重要な役割を果たします。特に、クラスのプロパティやメソッドへのアクセスレベルを制御することで、継承されたクラスの設計や機能を保護し、コードの再利用性と安全性を高めることができます。

パブリックアクセス指定子と継承

パブリックプロパティやメソッドは、親クラスとサブクラスの両方からアクセス可能です。これは、継承されたクラスが親クラスの機能を自由に利用できることを意味します。

class Vehicle {
    constructor(make, model) {
        this.make = make;
        this.model = model;
    }

    getDetails() {
        return `${this.make} ${this.model}`;
    }
}

class Car extends Vehicle {
    constructor(make, model, doors) {
        super(make, model);
        this.doors = doors;
    }

    getCarDetails() {
        return `${this.getDetails()}, Doors: ${this.doors}`;
    }
}

const myCar = new Car('Toyota', 'Corolla', 4);
console.log(myCar.getCarDetails()); // Toyota Corolla, Doors: 4

この例では、makemodelプロパティ、getDetailsメソッドはパブリックであり、VehicleクラスとCarクラスの両方からアクセスできます。

プライベートアクセス指定子と継承

プライベートプロパティやメソッドは、親クラスからサブクラスに継承されません。これは、プライベートメンバがクラスの外部から直接アクセスされないことを保証するためです。

class BankAccount {
    #balance;

    constructor(initialBalance) {
        this.#balance = initialBalance;
    }

    deposit(amount) {
        if (amount > 0) {
            this.#balance += amount;
        }
    }

    getBalance() {
        return this.#balance;
    }
}

class SavingsAccount extends BankAccount {
    constructor(initialBalance, interestRate) {
        super(initialBalance);
        this.interestRate = interestRate;
    }

    addInterest() {
        const interest = this.getBalance() * (this.interestRate / 100);
        this.deposit(interest);
    }
}

const savings = new SavingsAccount(1000, 5);
savings.addInterest();
console.log(savings.getBalance()); // 1050

この例では、#balanceBankAccountクラスのプライベートプロパティであり、SavingsAccountクラスから直接アクセスすることはできません。しかし、getBalanceメソッドを通じて間接的にアクセスしています。

プロテクテッドアクセス指定子と継承(疑似的な実装)

プロテクテッドプロパティやメソッドは、親クラスとサブクラスの両方からアクセス可能です。JavaScriptでは、命名規則を用いてプロテクテッドを疑似的に実装することができます。

class Animal {
    _type;

    constructor(type) {
        this._type = type;
    }

    _getType() {
        return this._type;
    }
}

class Dog extends Animal {
    constructor(type, breed) {
        super(type);
        this.breed = breed;
    }

    getDetails() {
        return `Type: ${this._getType()}, Breed: ${this.breed}`;
    }
}

const myDog = new Dog('Mammal', 'Golden Retriever');
console.log(myDog.getDetails()); // Type: Mammal, Breed: Golden Retriever

この例では、_typeプロパティと_getTypeメソッドはプロテクテッドと見なされ、AnimalクラスおよびそのサブクラスDogからアクセスされます。

アクセス指定子の役割と影響

アクセス指定子は、クラスの設計とその継承関係において重要な役割を果たします。適切なアクセス指定子を使用することで、クラスの内部状態を保護し、不正なアクセスを防ぎながら、必要な機能をサブクラスに提供することができます。

  • パブリック:クラスの外部およびサブクラスから自由にアクセス可能
  • プライベート:クラスの内部からのみアクセス可能、サブクラスには継承されない
  • プロテクテッド(疑似的な実装):クラスの内部およびサブクラスからアクセス可能

次のセクションでは、ES6クラスとアクセス指定子を組み合わせた高度な利用例を紹介します。これにより、クラスの設計がどのように柔軟かつ安全に行えるかを具体的に示します。

ES6クラスとアクセス指定子の組み合わせ

ES6クラス構文とアクセス指定子を組み合わせることで、柔軟かつ安全なクラス設計が可能になります。ここでは、これらを利用した高度な実装例を紹介します。

パブリック、プライベート、プロテクテッドの組み合わせ

実際のプロジェクトでは、パブリック、プライベート、およびプロテクテッド(疑似的な実装)のアクセス指定子を適切に組み合わせることが重要です。以下の例では、ユーザーアカウント管理システムを構築します。

class UserAccount {
    #password;
    _email;

    constructor(username, email, password) {
        this.username = username;
        this._email = email;
        this.#password = password;
    }

    #encryptPassword(password) {
        // 簡易的な暗号化の例
        return password.split('').reverse().join('');
    }

    setPassword(newPassword) {
        this.#password = this.#encryptPassword(newPassword);
    }

    checkPassword(password) {
        return this.#password === this.#encryptPassword(password);
    }

    getEmail() {
        return this._email;
    }
}

class AdminAccount extends UserAccount {
    constructor(username, email, password, adminCode) {
        super(username, email, password);
        this.adminCode = adminCode;
    }

    setAdminCode(newCode) {
        this.adminCode = newCode;
    }

    getAdminInfo() {
        return `Admin: ${this.username}, Email: ${this.getEmail()}`;
    }
}

const admin = new AdminAccount('adminUser', 'admin@example.com', 'securepassword', 'admin123');
console.log(admin.getAdminInfo()); // Admin: adminUser, Email: admin@example.com
console.log(admin.checkPassword('securepassword')); // true
admin.setPassword('newsecurepassword');
console.log(admin.checkPassword('securepassword')); // false
console.log(admin.checkPassword('newsecurepassword')); // true

この例では、以下のようにアクセス指定子を使い分けています。

  • #password(プライベート):クラス外部から直接アクセスできないようにし、パスワードの暗号化と検証を管理します。
  • _email(プロテクテッド):サブクラスからアクセス可能なプロパティとして定義され、親クラスとサブクラスで使用されます。
  • usernameおよびadminCode(パブリック):クラス外部から直接アクセスでき、ユーザー名と管理者コードの設定および取得に使用されます。

アクセス指定子を活用したデータ保護

アクセス指定子を利用して、データの保護と一貫性を確保する方法を紹介します。以下の例では、銀行口座クラスを実装します。

class BankAccount {
    #balance;
    _accountNumber;

    constructor(accountNumber, initialBalance) {
        this._accountNumber = accountNumber;
        this.#balance = initialBalance;
    }

    deposit(amount) {
        if (amount > 0) {
            this.#balance += amount;
            return true;
        }
        return false;
    }

    withdraw(amount) {
        if (amount > 0 && amount <= this.#balance) {
            this.#balance -= amount;
            return true;
        }
        return false;
    }

    getBalance() {
        return this.#balance;
    }
}

class SavingsAccount extends BankAccount {
    constructor(accountNumber, initialBalance, interestRate) {
        super(accountNumber, initialBalance);
        this.interestRate = interestRate;
    }

    applyInterest() {
        const interest = this.getBalance() * (this.interestRate / 100);
        this.deposit(interest);
    }

    getAccountInfo() {
        return `Account Number: ${this._accountNumber}, Balance: ${this.getBalance()}`;
    }
}

const savings = new SavingsAccount('123456789', 1000, 5);
savings.applyInterest();
console.log(savings.getAccountInfo()); // Account Number: 123456789, Balance: 1050
savings.withdraw(200);
console.log(savings.getAccountInfo()); // Account Number: 123456789, Balance: 850

この例では、以下の点に注意してください。

  • #balance(プライベート):クラス外部から直接アクセスできないようにし、口座残高の操作を専用メソッドを通じて行います。
  • _accountNumber(プロテクテッド):サブクラスからアクセス可能で、親クラスとサブクラスで利用されます。
  • interestRate(パブリック):クラス外部から直接アクセスできるため、利率の設定や取得に使用されます。

アクセス指定子とデザインパターン

アクセス指定子を利用することで、デザインパターンの実装もより効果的になります。以下の例では、シングルトンパターンを実装します。

class Singleton {
    static #instance;

    constructor(data) {
        if (Singleton.#instance) {
            throw new Error('Instance already exists. Use Singleton.getInstance()');
        }
        this.data = data;
        Singleton.#instance = this;
    }

    static getInstance(data) {
        if (!Singleton.#instance) {
            Singleton.#instance = new Singleton(data);
        }
        return Singleton.#instance;
    }

    getData() {
        return this.data;
    }

    setData(data) {
        this.data = data;
    }
}

const singleton1 = Singleton.getInstance('Initial Data');
console.log(singleton1.getData()); // Initial Data

const singleton2 = Singleton.getInstance();
singleton2.setData('Updated Data');
console.log(singleton1.getData()); // Updated Data

// const singleton3 = new Singleton('New Data'); // エラー: Instance already exists. Use Singleton.getInstance()

この例では、以下の点に注意してください。

  • #instance(プライベート):クラスのインスタンスを一意に管理し、外部から直接アクセスできないようにします。
  • data(パブリック):クラスのデータを管理し、必要に応じてアクセスおよび変更が可能です。

次のセクションでは、アクセス指定子に関連するよくあるエラーとその対処法を解説します。これにより、開発中に遭遇する可能性のある問題を効果的に解決できるようになります。

よくあるエラーとその対処法

アクセス指定子を使用する際に、開発者が遭遇する可能性のあるよくあるエラーとその対処法を理解しておくことは重要です。ここでは、パブリック、プライベート、およびプロテクテッド(疑似的な実装)のアクセス指定子に関連する一般的なエラーとその解決方法を解説します。

プライベートプロパティへの不正アクセス

プライベートプロパティやメソッドにクラス外部からアクセスしようとするとエラーが発生します。この問題は、プライベートプロパティの適切な使用方法を理解することで回避できます。

class User {
    #password;

    constructor(password) {
        this.#password = password;
    }

    checkPassword(inputPassword) {
        return this.#password === inputPassword;
    }
}

const user = new User('securepassword');
// console.log(user.#password); // エラー: プライベートプロパティにはアクセスできません
console.log(user.checkPassword('securepassword')); // true

対処法:プライベートプロパティやメソッドには、クラスの外部から直接アクセスしないようにし、必要な場合はパブリックメソッドを通じてアクセスするようにします。

プライベートメソッドの呼び出しエラー

プライベートメソッドをクラス外部から呼び出そうとするとエラーが発生します。プライベートメソッドは、クラスの内部でのみ使用されるべきです。

class BankAccount {
    #balance;

    constructor(initialBalance) {
        this.#balance = initialBalance;
    }

    #calculateInterest(rate) {
        return this.#balance * (rate / 100);
    }

    addInterest(rate) {
        const interest = this.#calculateInterest(rate);
        this.#balance += interest;
    }

    getBalance() {
        return this.#balance;
    }
}

const account = new BankAccount(1000);
// console.log(account.#calculateInterest(5)); // エラー: プライベートメソッドにはアクセスできません
account.addInterest(5);
console.log(account.getBalance()); // 1050

対処法:プライベートメソッドはクラス内部からのみ呼び出し、外部から直接呼び出さないようにします。

プロテクテッドプロパティへのアクセスエラー(疑似実装)

プロテクテッドプロパティ(疑似的な実装)へのアクセスエラーは、親クラスとサブクラスの間で適切にアクセスされていない場合に発生します。

class Animal {
    _species;

    constructor(species) {
        this._species = species;
    }

    getSpecies() {
        return this._species;
    }
}

class Bird extends Animal {
    constructor(species, canFly) {
        super(species);
        this.canFly = canFly;
    }

    getDetails() {
        return `${this.getSpecies()} can fly: ${this.canFly}`;
    }
}

const eagle = new Bird('Eagle', true);
console.log(eagle.getDetails()); // Eagle can fly: true
// console.log(eagle._species); // アクセスは可能だが、推奨されない

対処法:プロテクテッドプロパティ(疑似的な実装)は、サブクラスからのみアクセスし、クラス外部からのアクセスは避けるようにします。

インスタンス化時のエラー

クラスを適切にインスタンス化しないと、エラーが発生することがあります。特に、親クラスのコンストラクタを呼び出さない場合に問題が発生します。

class Vehicle {
    constructor(make, model) {
        this.make = make;
        this.model = model;
    }
}

class Car extends Vehicle {
    constructor(make, model, doors) {
        super(make, model); // 親クラスのコンストラクタを呼び出す
        this.doors = doors;
    }
}

const car = new Car('Toyota', 'Corolla', 4);
console.log(car);

対処法:サブクラスのコンストラクタ内でsuperキーワードを使用して親クラスのコンストラクタを呼び出すことを忘れないようにします。

不適切なアクセス指定子の使用

アクセス指定子を適切に使用しないと、意図しないアクセスやセキュリティの問題が発生することがあります。

class Person {
    constructor(name, age) {
        this._name = name; // プライベートであるべきものをプロテクテッドにしてしまう
        this._age = age;
    }

    getDetails() {
        return `${this._name} is ${this._age} years old.`;
    }
}

const person = new Person('John', 30);
console.log(person._name); // アクセスは可能だが、推奨されない

対処法:プロパティやメソッドのアクセスレベルを慎重に設計し、適切なアクセス指定子を使用するようにします。

次のセクションでは、理解を深めるための演習問題を提示します。これにより、学んだ内容を実際に試してみることができます。

演習問題

ここでは、この記事で学んだ内容を実践するための演習問題を提示します。これらの演習問題を通じて、ES6クラス構文とアクセス指定子の理解を深めましょう。

演習1: パブリックプロパティとメソッド

以下の指示に従って、Bookクラスを実装してください。

  1. title(パブリックプロパティ)とauthor(パブリックプロパティ)を持つBookクラスを作成します。
  2. getDetails(パブリックメソッド)を追加し、本のタイトルと著者を表示します。
class Book {
    constructor(title, author) {
        // プロパティの初期化
    }

    getDetails() {
        // タイトルと著者を表示
    }
}

const myBook = new Book('1984', 'George Orwell');
console.log(myBook.getDetails()); // 出力: 1984 by George Orwell

演習2: プライベートプロパティとメソッド

以下の指示に従って、CreditCardクラスを実装してください。

  1. number(プライベートプロパティ)とcvv(プライベートプロパティ)を持つCreditCardクラスを作成します。
  2. encryptData(プライベートメソッド)を追加し、カード情報を暗号化します。
  3. getEncryptedDetails(パブリックメソッド)を追加し、暗号化されたカード情報を返します。
class CreditCard {
    constructor(number, cvv) {
        // プロパティの初期化
    }

    // プライベートメソッド
    #encryptData(data) {
        // 簡易的な暗号化処理
    }

    getEncryptedDetails() {
        // 暗号化されたカード情報を返す
    }
}

const myCard = new CreditCard('1234567890123456', '123');
console.log(myCard.getEncryptedDetails()); // 出力例: 6543210987654321 321

演習3: プロテクテッドプロパティとメソッド(疑似実装)

以下の指示に従って、Employeeクラスを実装してください。

  1. _id(プロテクテッドプロパティ)とname(パブリックプロパティ)を持つEmployeeクラスを作成します。
  2. _getDetails(プロテクテッドメソッド)を追加し、従業員のIDと名前を返します。
  3. ManagerクラスをEmployeeクラスから継承し、department(パブリックプロパティ)を追加します。
  4. getManagerInfo(パブリックメソッド)を追加し、マネージャーの情報を表示します。
class Employee {
    constructor(id, name) {
        // プロパティの初期化
    }

    _getDetails() {
        // IDと名前を返す
    }
}

class Manager extends Employee {
    constructor(id, name, department) {
        // 親クラスのコンストラクタを呼び出す
        // departmentプロパティの初期化
    }

    getManagerInfo() {
        // マネージャーの情報を表示
    }
}

const manager = new Manager(1, 'Alice', 'HR');
console.log(manager.getManagerInfo()); // 出力: ID: 1, Name: Alice, Department: HR

演習4: アクセス指定子と継承

以下の指示に従って、VehicleクラスとBikeクラスを実装してください。

  1. make(パブリックプロパティ)とmodel(パブリックプロパティ)を持つVehicleクラスを作成します。
  2. getDetails(パブリックメソッド)を追加し、車の詳細を表示します。
  3. BikeクラスをVehicleクラスから継承し、type(パブリックプロパティ)を追加します。
  4. getBikeDetails(パブリックメソッド)を追加し、バイクの詳細を表示します。
class Vehicle {
    constructor(make, model) {
        // プロパティの初期化
    }

    getDetails() {
        // 車の詳細を表示
    }
}

class Bike extends Vehicle {
    constructor(make, model, type) {
        // 親クラスのコンストラクタを呼び出す
        // typeプロパティの初期化
    }

    getBikeDetails() {
        // バイクの詳細を表示
    }
}

const myBike = new Bike('Yamaha', 'MT-07', 'Sport');
console.log(myBike.getBikeDetails()); // 出力: Make: Yamaha, Model: MT-07, Type: Sport

これらの演習問題を通じて、アクセス指定子の使用方法とその効果を実際に試すことができます。各演習を解決することで、ES6クラス構文とアクセス指定子の理解がさらに深まるでしょう。

次のセクションでは、この記事のまとめを行います。

まとめ

この記事では、JavaScriptのES6クラス構文とアクセス指定子について詳細に解説しました。ES6クラス構文の基本から始まり、パブリック、プライベート、プロテクテッド(疑似的な実装)のアクセス指定子の概念とその使い方を学びました。各アクセス指定子の具体的な実践例を通じて、クラス設計におけるデータの保護とアクセス制御の重要性を理解しました。

アクセス指定子を適切に使用することで、クラスの内部状態を保護し、メソッドやプロパティへの不正アクセスを防ぐことができます。また、継承関係においても、各アクセス指定子を適用することで、サブクラスと親クラスの間でのデータ共有を効果的に管理できます。

最後に、演習問題を通じて実際にコードを記述し、理解を深めることができました。これにより、理論的な知識だけでなく、実践的なスキルも向上させることができました。今後のプロジェクトで、この記事で学んだ知識を活用し、より堅牢でメンテナンスしやすいコードを作成できることを願っています。

コメント

コメントする

目次