JavaScriptのアクセス指定子を使ったオブジェクト指向プログラミング徹底解説

JavaScriptは、長い間、オブジェクト指向プログラミング(OOP)をサポートしてきましたが、アクセス指定子という概念が本格的に導入されたのは比較的最近のことです。アクセス指定子は、クラスのメンバー変数やメソッドに対するアクセス範囲を制御するためのもので、コードのカプセル化とセキュリティを向上させます。本記事では、JavaScriptのアクセス指定子について詳しく解説し、実際にどのように活用できるかを具体的な例を交えて説明します。これにより、JavaScriptでのオブジェクト指向プログラミングの理解を深め、より堅牢でメンテナブルなコードを書くための知識を習得することができます。

目次

アクセス指定子とは

アクセス指定子は、クラスのプロパティやメソッドへのアクセスレベルを制御するためのキーワードです。これにより、外部からの不正アクセスを防ぎ、クラス内部のデータの一貫性を保つことができます。JavaScriptでは、主に以下の3つのアクセス指定子が使用されます。

public

public指定子は、クラスの外部からでも自由にアクセス可能なメンバーを定義します。デフォルトでは、全てのクラスメンバーはpublicです。

private

private指定子は、クラスの外部からはアクセスできないメンバーを定義します。内部のデータを隠蔽し、直接操作を防ぐために使用されます。

protected

protected指定子は、クラス自身とそのサブクラスからのみアクセス可能なメンバーを定義します。これは、継承関係にあるクラス間でデータを共有するために使用されますが、JavaScriptの標準仕様には含まれておらず、通常は#記号を使って擬似的に表現します。

これらのアクセス指定子を適切に使用することで、クラスの設計をより安全かつ効率的に行うことができます。

JavaScriptにおけるアクセス指定子の歴史

JavaScriptはもともと手軽なスクリプト言語として開発され、長らくアクセス指定子という概念は存在しませんでした。しかし、JavaScriptの利用範囲が広がるにつれ、より堅牢でメンテナブルなコードを書くためのオブジェクト指向プログラミング(OOP)の必要性が高まりました。

初期のJavaScriptとカプセル化

初期のJavaScriptでは、すべてのクラスメンバーがパブリックであり、プライベートなメンバーを作成するためにはクロージャを使ったり、名前にアンダースコアを付けるなどの慣習的な方法が用いられていました。この方法は手間がかかり、完全なデータ隠蔽を実現するのは困難でした。

ES6以降の進化

2015年に導入されたECMAScript 6(ES6)でクラス構文が追加され、JavaScriptでのオブジェクト指向プログラミングが大幅に改善されました。しかし、ES6クラスでも完全なプライベートメンバーのサポートはなく、依然としてデータ隠蔽は課題の一つでした。

ES2019とプライベートフィールド

2019年に正式に導入されたES2019(ECMAScript 2019)では、プライベートフィールドとプライベートメソッドがサポートされるようになりました。これにより、クラスの内部で#記号を使ってプライベートメンバーを定義し、外部からのアクセスを防ぐことが可能となりました。

現在と将来

現在、JavaScriptは公的にサポートされているアクセス指定子としてpublicprivateを持ち、多くの開発者がこれらを利用して堅牢なオブジェクト指向設計を実現しています。今後もJavaScriptの進化に伴い、さらに多様なアクセス指定子や関連機能が追加されることが期待されます。

このように、JavaScriptのアクセス指定子の導入は、言語の進化とともに開発者のニーズに応える形で行われてきました。これにより、より安全でメンテナブルなコードを書くための基盤が整備されています。

public指定子の使い方

public指定子は、クラスの外部から自由にアクセスできるメンバーを定義します。JavaScriptでは、特に指定がない限り、すべてのクラスメンバーはpublicとして扱われます。このため、public指定子を明示的に書くことはありませんが、他の言語と同様にアクセス範囲を明示するために説明します。

publicメンバーの宣言と使用方法

以下の例は、publicメンバーを持つクラスの基本的な構造を示しています。

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

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

const john = new Person('John', 30);
console.log(john.name); // John
console.log(john.age);  // 30
john.greet(); // Hello, my name is John and I am 30 years old.

この例では、nameageというプロパティ、およびgreetというメソッドが定義されています。どれもpublicであり、クラスの外部から自由にアクセスできます。

public指定子の利点

public指定子の主な利点は、クラスの外部からメンバーに自由にアクセスできることです。これにより、クラスのインスタンスを操作したり、情報を取得したりする際に便利です。

  1. シンプルなアクセス: クラスのメンバーが公開されているため、他のオブジェクトや関数から直接アクセスできます。
  2. 柔軟性: メンバーの値を容易に変更できるため、動的な挙動が求められる場合に適しています。

publicメンバーの注意点

publicメンバーは外部から直接アクセス可能であるため、次の点に注意が必要です。

  1. データの保護: 外部から自由に変更できるため、不適切なデータ変更によるバグが発生するリスクがあります。
  2. 一貫性の維持: クラスの内部状態が簡単に変わるため、一貫性を保つための追加のロジックが必要になる場合があります。

このように、public指定子を使うことで、クラスのメンバーに簡単にアクセスできる反面、データの保護と一貫性の維持に注意が必要です。適切に使用することで、柔軟で強力なクラス設計が可能となります。

private指定子の使い方

private指定子は、クラスの外部からアクセスできないメンバーを定義します。これにより、データの隠蔽とクラス内部の実装詳細の保護が可能となります。JavaScriptでは、privateフィールドとメソッドは#記号を使用して定義されます。

privateメンバーの宣言と使用方法

以下の例は、privateメンバーを持つクラスの基本的な構造を示しています。

class Person {
    #name;  // private
    #age;   // private

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

    #calculateBirthYear() { // private method
        const currentYear = new Date().getFullYear();
        return currentYear - this.#age;
    }

    greet() { // public method
        console.log(`Hello, my name is ${this.#name} and I was born in ${this.#calculateBirthYear()}.`);
    }
}

const john = new Person('John', 30);
console.log(john.name); // undefined
console.log(john.age);  // undefined
john.greet(); // Hello, my name is John and I was born in 1994.

この例では、#name#ageというプロパティ、および#calculateBirthYearというメソッドが定義されています。これらはすべてprivateであり、クラスの外部からアクセスできません。

private指定子の利点

private指定子の主な利点は、クラス内部のデータとメソッドを隠蔽できることです。これにより、次の利点が得られます。

  1. データの保護: クラス外部から直接アクセスできないため、意図しない変更や不正なアクセスを防ぎます。
  2. カプセル化の促進: クラスの内部実装を隠すことで、外部からの依存を減らし、モジュール性とメンテナンス性を向上させます。
  3. 一貫性の維持: 内部データの一貫性を保つための追加ロジックを簡単に実装できます。

privateメンバーの注意点

privateメンバーを使用する際には、以下の点に注意が必要です。

  1. テストの難易度: privateメンバーは外部からアクセスできないため、ユニットテストが難しくなる場合があります。そのため、テストのために設計を工夫する必要があります。
  2. 性能のオーバーヘッド: プライベートフィールドはクラス定義の際に追加の処理を伴うため、パフォーマンスに若干の影響を与えることがあります。

このように、private指定子を使用することで、クラスのデータ保護とカプセル化を実現できます。適切に活用することで、堅牢でメンテナンスしやすいコードを書くことが可能となります。

protected指定子の使用

protected指定子は、クラス自身とそのサブクラスからのみアクセス可能なメンバーを定義します。JavaScriptの標準仕様にはprotected指定子は含まれていませんが、他の言語と同様の概念を擬似的に実装する方法があります。

擬似的なprotectedメンバーの実装方法

JavaScriptでprotectedメンバーを実装するためには、特定の命名規則やシンボルを利用することが一般的です。以下の例では、命名規則としてアンダースコア(_)を使用します。

class Person {
    constructor(name, age) {
        this._name = name; // protected
        this._age = age;   // protected
    }

    greet() { // public method
        console.log(`Hello, my name is ${this._name} and I am ${this._age} years old.`);
    }
}

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

    introduce() { // public method
        console.log(`Hello, my name is ${this._name}, I am ${this._age} years old, and I work as a ${this._jobTitle}.`);
    }
}

const jane = new Employee('Jane', 28, 'Software Engineer');
jane.greet(); // Hello, my name is Jane and I am 28 years old.
jane.introduce(); // Hello, my name is Jane, I am 28 years old, and I work as a Software Engineer.

この例では、_name_age、および_jobTitleというプロパティが擬似的なprotectedメンバーとして定義されています。これらは、クラス外部からアクセスするべきではないことを示唆する命名規則です。

protected指定子の利点

protected指定子の主な利点は、継承関係にあるクラス間でデータを共有できることです。これにより、次の利点が得られます。

  1. 継承の柔軟性: サブクラスが親クラスの内部データにアクセスできるため、継承を使ったクラス設計が柔軟になります。
  2. 内部データの一貫性: 親クラスとサブクラス間で共通の内部データを使用することで、一貫性を保ちながら機能を拡張できます。

protectedメンバーの注意点

protectedメンバーを使用する際には、以下の点に注意が必要です。

  1. 設計の複雑さ: 継承関係が複雑になると、protectedメンバーの管理も複雑になります。設計をシンプルに保つことが重要です。
  2. アクセス制御の限界: JavaScriptでは完全なprotected指定子をサポートしていないため、命名規則に依存する擬似的な方法では外部からのアクセスを完全に防ぐことはできません。

このように、JavaScriptでprotected指定子を擬似的に使用することで、継承関係における柔軟なデータ共有を実現できます。適切に設計することで、クラス間の連携を強化し、コードの再利用性を高めることが可能です。

アクセス指定子の実装例

ここでは、publicprivate、および擬似的なprotected指定子を使用したクラスの実装例を紹介します。これにより、各指定子の使い方とその効果を具体的に理解できます。

例1: public指定子の使用

public指定子を使ってクラスメンバーを定義します。JavaScriptでは明示的にpublicと記述する必要はありません。

class Car {
    constructor(make, model) {
        this.make = make;   // public
        this.model = model; // public
    }

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

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

例2: private指定子の使用

private指定子を使ってクラスメンバーを定義します。#記号を用いてプライベートメンバーを宣言します。

class BankAccount {
    #accountNumber;  // private
    #balance;        // private

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

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

    getBalance() { // public method
        return this.#balance;
    }
}

const myAccount = new BankAccount('123456789', 1000);
console.log(myAccount.#accountNumber); // SyntaxError: Private field '#accountNumber' must be declared in an enclosing class
console.log(myAccount.getBalance()); // 1000
myAccount.deposit(500);
console.log(myAccount.getBalance()); // 1500

例3: 擬似的なprotected指定子の使用

protected指定子を擬似的に実装するために、アンダースコア(_)を使用してメンバーを宣言します。

class Employee {
    constructor(name, salary) {
        this._name = name;   // protected
        this._salary = salary; // protected
    }

    getDetails() { // public method
        return `${this._name} earns ${this._salary}`;
    }
}

class Manager extends Employee {
    constructor(name, salary, department) {
        super(name, salary);
        this._department = department; // protected
    }

    getDetails() { // overridden public method
        return `${this._name} manages the ${this._department} department and earns ${this._salary}`;
    }
}

const emp = new Employee('Alice', 50000);
console.log(emp.getDetails()); // Alice earns 50000

const mgr = new Manager('Bob', 80000, 'Engineering');
console.log(mgr.getDetails()); // Bob manages the Engineering department and earns 80000

これらの例を通じて、publicprivate、および擬似的なprotected指定子の使い方とその効果を理解できます。それぞれの指定子を適切に使用することで、クラスの設計をより安全かつ効率的に行うことができます。

カプセル化とアクセス指定子

カプセル化は、オブジェクト指向プログラミングの基本原則の一つであり、オブジェクトの内部状態を隠蔽し、外部からの不正なアクセスや変更を防ぐことを目的としています。アクセス指定子は、このカプセル化を実現するための重要なツールです。

カプセル化の概念

カプセル化とは、オブジェクトのデータ(プロパティ)とそれに関連するメソッド(関数)を一つにまとめ、外部から直接アクセスできないようにすることです。これにより、オブジェクトの内部状態を保護し、一貫性を保つことができます。

アクセス指定子とカプセル化

アクセス指定子(publicprivateprotected)を使用することで、クラスのメンバーに対するアクセス制御を行い、カプセル化を実現します。

publicメンバーとカプセル化

publicメンバーは、クラスの外部から自由にアクセスできるため、カプセル化の効果はありません。しかし、クラスの外部に公開する必要があるメソッドやプロパティにはpublicを使用します。

privateメンバーとカプセル化

privateメンバーはクラスの内部からのみアクセス可能です。これにより、データの隠蔽が行われ、外部からの不正なアクセスや変更を防ぐことができます。

class User {
    #password;  // private

    constructor(username, password) {
        this.username = username; // public
        this.#password = password;
    }

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

const user = new User('Alice', 'secret');
console.log(user.username); // Alice
console.log(user.#password); // SyntaxError: Private field '#password' must be declared in an enclosing class
console.log(user.checkPassword('secret')); // true

protectedメンバーとカプセル化

JavaScriptでは標準的なprotected指定子はサポートされていませんが、アンダースコアを使って擬似的に実装することで、親クラスとサブクラス間でデータを共有しながらカプセル化を維持できます。

class Account {
    constructor(accountNumber, balance) {
        this._accountNumber = accountNumber; // protected
        this._balance = balance; // protected
    }

    getBalance() { // public method
        return this._balance;
    }
}

class SavingsAccount extends Account {
    constructor(accountNumber, balance, interestRate) {
        super(accountNumber, balance);
        this._interestRate = interestRate; // protected
    }

    calculateInterest() { // public method
        return this._balance * this._interestRate;
    }
}

const savings = new SavingsAccount('123456', 1000, 0.05);
console.log(savings.getBalance()); // 1000
console.log(savings.calculateInterest()); // 50
console.log(savings._accountNumber); // '123456' (should not be accessed directly)

カプセル化の利点

カプセル化を正しく行うことで、以下の利点が得られます。

  1. データ保護: オブジェクトの内部データが外部から保護され、不正なアクセスや変更が防止されます。
  2. 一貫性の維持: クラスの内部状態を一貫して保つことができ、バグの発生を減少させます。
  3. 柔軟な変更: 内部実装を隠蔽することで、外部に影響を与えることなくクラスの実装を変更できます。
  4. メンテナンスの容易さ: クラスの使用方法が明確になり、コードのメンテナンスが容易になります。

このように、アクセス指定子を使用してカプセル化を実現することで、JavaScriptのオブジェクト指向プログラミングの品質と信頼性を大幅に向上させることができます。

アクセス指定子を使ったクラス設計

アクセス指定子を効果的に使用することで、クラスの設計をより堅牢でメンテナンスしやすくすることができます。ここでは、アクセス指定子を活用したクラス設計のベストプラクティスを紹介します。

シンプルなクラス設計

まずは、アクセス指定子を使ってシンプルなクラスを設計してみましょう。この例では、ユーザーアカウントを管理するクラスを作成します。

class UserAccount {
    #username;  // private
    #password;  // private

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

    getUsername() { // public method
        return this.#username;
    }

    checkPassword(password) { // public method
        return this.#password === password;
    }
}

const account = new UserAccount('Alice', 'password123');
console.log(account.getUsername()); // Alice
console.log(account.checkPassword('password123')); // true
console.log(account.#password); // SyntaxError: Private field '#password' must be declared in an enclosing class

継承を使ったクラス設計

次に、継承を用いたクラス設計の例を見てみましょう。ここでは、基本的なPersonクラスと、それを拡張したEmployeeクラスを作成します。

class Person {
    _name;  // protected
    _age;   // protected

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

    getDetails() { // public method
        return `${this._name}, ${this._age} years old`;
    }
}

class Employee extends Person {
    #employeeId;  // private

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

    getEmployeeDetails() { // public method
        return `${this.getDetails()}, Employee ID: ${this.#employeeId}`;
    }
}

const employee = new Employee('Bob', 35, 'E12345');
console.log(employee.getDetails()); // Bob, 35 years old
console.log(employee.getEmployeeDetails()); // Bob, 35 years old, Employee ID: E12345
console.log(employee.#employeeId); // SyntaxError: Private field '#employeeId' must be declared in an enclosing class

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

最後に、publicprivate、およびprotected指定子を組み合わせて使用する例を示します。これにより、クラスのメンバーへのアクセスを細かく制御し、より安全で整理されたコードを作成できます。

class BankAccount {
    #accountNumber;  // private
    #balance;        // private
    _accountType;    // protected

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

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

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

    getBalance() { // public method
        return this.#balance;
    }

    getAccountType() { // public method
        return this._accountType;
    }
}

class SavingsAccount extends BankAccount {
    constructor(accountNumber, initialBalance) {
        super(accountNumber, initialBalance, 'Savings');
    }

    addInterest(rate) { // public method
        const interest = this.getBalance() * rate;
        this.deposit(interest);
    }
}

const savings = new SavingsAccount('987654321', 1000);
console.log(savings.getAccountType()); // Savings
savings.deposit(500);
console.log(savings.getBalance()); // 1500
savings.addInterest(0.05);
console.log(savings.getBalance()); // 1575

このように、アクセス指定子を適切に組み合わせることで、クラスの内部データを保護しながら、必要な情報だけを外部に公開することができます。これにより、クラス設計がより堅牢で安全になり、メンテナンスも容易になります。

JavaScriptのモジュールとアクセス指定子

JavaScriptのモジュールシステムを使用すると、コードを個別のファイルに分割して整理し、再利用性とメンテナンス性を向上させることができます。モジュール内でアクセス指定子を使用することで、さらにデータのカプセル化と保護を強化することが可能です。

モジュールの基本

JavaScriptのモジュールは、ES6以降、importexportキーワードを使用して定義および使用されます。これにより、特定の機能を外部に公開する一方で、内部実装を隠蔽できます。

モジュールの作成と利用例

まず、基本的なモジュールの作成と利用方法を見てみましょう。以下の例では、math.jsというファイルに基本的な数学関数を定義し、それを他のファイルから利用します。

math.js(モジュールファイル):

// math.js
export function add(a, b) {
    return a + b;
}

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

const PI = 3.14159; // モジュール内部の定数(公開しない)

app.js(メインファイル):

// app.js
import { add, subtract } from './math.js';

console.log(add(2, 3)); // 5
console.log(subtract(5, 2)); // 3

この例では、addsubtract関数をエクスポートし、他のファイルからインポートして使用しています。一方で、PI定数はモジュール内でのみ使用され、外部には公開されません。

アクセス指定子とモジュール

モジュール内でアクセス指定子を使用することで、さらに高度なデータのカプセル化を実現できます。ここでは、クラスをモジュールとしてエクスポートし、privateメンバーを使用する例を紹介します。

user.js(モジュールファイル):

// user.js
class User {
    #username;  // private
    #password;  // private

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

    getUsername() { // public method
        return this.#username;
    }

    checkPassword(password) { // public method
        return this.#password === password;
    }
}

export default User;

main.js(メインファイル):

// main.js
import User from './user.js';

const user = new User('Alice', 'password123');
console.log(user.getUsername()); // Alice
console.log(user.checkPassword('password123')); // true

この例では、Userクラスをエクスポートし、そのprivateメンバーを使用してデータのカプセル化を実現しています。Userクラスの内部データは外部からアクセスできないため、データの保護が強化されます。

モジュールを使った設計の利点

モジュールシステムを使用してアクセス指定子を組み合わせることで、以下の利点が得られます。

  1. 再利用性の向上: モジュール化されたコードは、他のプロジェクトでも簡単に再利用できます。
  2. メンテナンス性の向上: コードが整理され、明確なインターフェースを持つため、メンテナンスが容易になります。
  3. カプセル化の強化: アクセス指定子を使用してモジュール内部のデータを保護し、外部からの不正なアクセスを防ぎます。

このように、JavaScriptのモジュールシステムとアクセス指定子を組み合わせて使用することで、より堅牢でメンテナンスしやすいクラス設計が可能となります。これにより、プロジェクト全体の品質と信頼性を向上させることができます。

アクセス指定子を使ったテスト方法

アクセス指定子を使用することでクラスのデータを隠蔽し、外部からのアクセスを制限できますが、テストを行う際にはこれが障害となる場合があります。ここでは、アクセス指定子を考慮したユニットテストの方法を紹介します。

publicメンバーのテスト

publicメンバーは外部から自由にアクセスできるため、テストは容易です。以下の例では、publicメンバーを持つクラスのテストを行います。

// calculator.js
export class Calculator {
    add(a, b) {
        return a + b;
    }

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

// calculator.test.js
import { Calculator } from './calculator.js';

const calculator = new Calculator();

console.assert(calculator.add(2, 3) === 5, 'Addition test failed');
console.assert(calculator.subtract(5, 2) === 3, 'Subtraction test failed');
console.log('All tests passed');

privateメンバーのテスト

privateメンバーは直接アクセスできないため、テストを行う際にはpublicメソッドを通じて間接的にテストします。

// bankAccount.js
export class BankAccount {
    #balance; // private

    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;
    }
}

// bankAccount.test.js
import { BankAccount } from './bankAccount.js';

const account = new BankAccount(1000);

console.assert(account.getBalance() === 1000, 'Initial balance test failed');
account.deposit(500);
console.assert(account.getBalance() === 1500, 'Deposit test failed');
account.withdraw(200);
console.assert(account.getBalance() === 1300, 'Withdrawal test failed');
console.log('All tests passed');

擬似的なprotectedメンバーのテスト

擬似的なprotectedメンバーは、テストする際にサブクラスを使ってアクセスできます。

// employee.js
export class Employee {
    constructor(name, salary) {
        this._name = name; // protected
        this._salary = salary; // protected
    }

    getSalary() {
        return this._salary;
    }
}

// employee.test.js
import { Employee } from './employee.js';

const employee = new Employee('John', 50000);

console.assert(employee.getSalary() === 50000, 'Salary test failed');
console.log('All tests passed');

モジュールを利用したテストの工夫

モジュールシステムを使用することで、テストしやすい設計を行うことができます。モジュール内でテスト専用のエクスポートを追加することも一つの方法です。

user.js(モジュールファイル):

// user.js
class User {
    #username;  // private
    #password;  // private

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

    getUsername() { // public method
        return this.#username;
    }

    checkPassword(password) { // public method
        return this.#password === password;
    }
}

export default User;

// テスト専用のエクスポート
export const _testUser = User;

user.test.js(テストファイル):

// user.test.js
import { _testUser as User } from './user.js';

const user = new User('Alice', 'password123');

console.assert(user.getUsername() === 'Alice', 'Username test failed');
console.assert(user.checkPassword('password123') === true, 'Password test failed');
console.log('All tests passed');

このように、アクセス指定子を考慮したテスト方法を実践することで、クラスのデータ保護を維持しながら、十分なテストカバレッジを確保できます。適切なテスト方法を選択することで、堅牢で信頼性の高いコードベースを維持することが可能です。

実践演習:アクセス指定子を用いたプロジェクト

ここでは、アクセス指定子を活用した実践的なプロジェクト例を紹介します。このプロジェクトでは、シンプルな銀行システムを作成し、publicprivate、および擬似的なprotected指定子を使ってクラス設計を行います。

プロジェクト概要

このプロジェクトでは、次の3つのクラスを作成します。

  1. BankAccountクラス:基本的な銀行口座の機能を持つクラス
  2. SavingsAccountクラス:BankAccountクラスを継承し、貯蓄口座の機能を追加
  3. Bankクラス:複数の口座を管理するクラス

BankAccountクラスの設計

BankAccountクラスでは、privateメンバーを使って口座番号と残高を管理し、publicメソッドを通じてアクセスできるようにします。

// bankAccount.js
export class BankAccount {
    #accountNumber;  // private
    #balance;        // private

    constructor(accountNumber, initialBalance) {
        this.#accountNumber = accountNumber;
        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;
    }

    getAccountNumber() {
        return this.#accountNumber;
    }
}

SavingsAccountクラスの設計

SavingsAccountクラスでは、BankAccountクラスを継承し、protectedメンバーを使って利率を管理します。

// savingsAccount.js
import { BankAccount } from './bankAccount.js';

export class SavingsAccount extends BankAccount {
    _interestRate;  // protected

    constructor(accountNumber, initialBalance, interestRate) {
        super(accountNumber, initialBalance);
        this._interestRate = interestRate;
    }

    addInterest() {
        const interest = this.getBalance() * this._interestRate;
        this.deposit(interest);
    }
}

Bankクラスの設計

Bankクラスでは、複数の口座を管理し、新しい口座を追加したり、既存の口座の残高を確認したりする機能を提供します。

// bank.js
import { BankAccount } from './bankAccount.js';
import { SavingsAccount } from './savingsAccount.js';

export class Bank {
    constructor() {
        this.accounts = [];
    }

    addAccount(account) {
        this.accounts.push(account);
    }

    getAccount(accountNumber) {
        return this.accounts.find(account => account.getAccountNumber() === accountNumber);
    }

    getTotalBalance() {
        return this.accounts.reduce((total, account) => total + account.getBalance(), 0);
    }
}

プロジェクトのテスト

作成したクラスのテストを行い、各機能が正しく動作することを確認します。

// bank.test.js
import { BankAccount } from './bankAccount.js';
import { SavingsAccount } from './savingsAccount.js';
import { Bank } from './bank.js';

// BankAccountのテスト
const account = new BankAccount('001', 1000);
console.assert(account.getBalance() === 1000, 'Initial balance test failed');
account.deposit(500);
console.assert(account.getBalance() === 1500, 'Deposit test failed');
account.withdraw(200);
console.assert(account.getBalance() === 1300, 'Withdrawal test failed');

// SavingsAccountのテスト
const savings = new SavingsAccount('002', 2000, 0.05);
console.assert(savings.getBalance() === 2000, 'Initial balance test failed');
savings.addInterest();
console.assert(savings.getBalance() === 2100, 'Interest test failed');

// Bankのテスト
const bank = new Bank();
bank.addAccount(account);
bank.addAccount(savings);
console.assert(bank.getTotalBalance() === 3400, 'Total balance test failed');

console.log('All tests passed');

このプロジェクトを通じて、アクセス指定子を活用したクラス設計の実践的な方法を学びました。適切なアクセス指定子を使用することで、データのカプセル化と保護を強化し、堅牢でメンテナンスしやすいコードを作成することができます。

まとめ

本記事では、JavaScriptのアクセス指定子を活用したオブジェクト指向プログラミングについて詳しく解説しました。publicprivate、および擬似的なprotected指定子を使用することで、クラスのデータ保護とカプセル化を強化し、より堅牢でメンテナンスしやすいコードを作成することができます。

アクセス指定子を正しく利用することで、次のような利点が得られます。

  1. データ保護: クラス内部のデータを外部から保護し、不正なアクセスや変更を防ぎます。
  2. カプセル化: 内部実装を隠蔽し、モジュール性とコードの再利用性を向上させます。
  3. 一貫性の維持: クラスの内部状態を一貫して保つことで、バグの発生を減少させます。
  4. 柔軟な設計: 継承関係やモジュールシステムを利用して、柔軟で拡張性の高いクラス設計が可能になります。

具体的なコード例やテスト方法を通じて、アクセス指定子の実践的な使い方を学びました。これにより、JavaScriptでのオブジェクト指向プログラミングの理解が深まり、より高品質なソフトウェア開発が可能になります。

今後もアクセス指定子を適切に活用し、セキュアでメンテナンスしやすいコードを作成していきましょう。

コメント

コメントする

目次