JavaScriptのクラスフィールドとアクセス指定子の基本概念と重要性を紹介します。
JavaScriptは、従来のプロトタイプベースのオブジェクト指向言語として知られていましたが、ES6以降、クラス構文が導入され、オブジェクト指向プログラミングがより直感的に行えるようになりました。このクラス構文の中で重要な概念の一つが「クラスフィールド」と「アクセス指定子」です。
クラスフィールドとは、クラスのインスタンスごとに異なるデータを保持するための変数を指します。これにより、オブジェクトの状態を管理することが容易になります。また、アクセス指定子は、クラス内のフィールドやメソッドに対するアクセスレベルを制御するためのキーワードです。これにより、カプセル化を実現し、オブジェクトの内部状態を外部から保護することができます。
本記事では、JavaScriptにおけるクラスフィールドとアクセス指定子の基本的な使い方から、実際の開発での応用例、エラーハンドリング、ベストプラクティスに至るまで、詳しく解説します。これにより、JavaScriptでのクラス設計がより効率的かつ効果的になるでしょう。
クラスフィールドとは何か
クラスフィールドは、クラスのインスタンスごとに異なるデータを保持するための変数です。JavaScriptにおいては、クラスの中で直接宣言され、クラスの各インスタンスが独自の値を持つことができます。
クラスフィールドの定義
クラスフィールドは、クラスの内部で次のように宣言します。
class Person {
name; // クラスフィールドの宣言
constructor(name) {
this.name = name; // クラスフィールドの初期化
}
}
const john = new Person("John");
console.log(john.name); // "John"
この例では、name
というクラスフィールドを持つPerson
クラスを定義しています。クラスフィールドは、constructor
内でthis
キーワードを使って初期化されます。
クラスフィールドの基本的な使用例
クラスフィールドを使用すると、各インスタンスに固有のデータを保持できます。例えば、次のようにして個々の人物の情報を管理できます。
class Person {
name;
age;
constructor(name, age) {
this.name = name;
this.age = age;
}
getDetails() {
return `${this.name} is ${this.age} years old.`;
}
}
const alice = new Person("Alice", 30);
const bob = new Person("Bob", 25);
console.log(alice.getDetails()); // "Alice is 30 years old."
console.log(bob.getDetails()); // "Bob is 25 years old."
この例では、Person
クラスはname
とage
という2つのクラスフィールドを持ち、それぞれのインスタンス(alice
とbob
)が異なる値を保持しています。これにより、オブジェクト指向の設計が直感的かつ明確になります。
クラスフィールドは、オブジェクトの状態を管理し、クラスのインスタンス間で一貫したデータ操作を行うための重要な要素です。次に、アクセス指定子の種類とその役割について詳しく見ていきます。
アクセス指定子の種類と役割
アクセス指定子は、クラスのフィールドやメソッドに対するアクセスレベルを制御するためのキーワードです。これにより、オブジェクトの内部状態を保護し、カプセル化を実現します。JavaScriptにおける主なアクセス指定子には、public
、private
、protected
があります。
public
public
アクセス指定子は、クラスのフィールドやメソッドが外部からアクセス可能であることを示します。JavaScriptでは、デフォルトで全てのクラスメンバーがpublic
とみなされます。
class Animal {
public name;
constructor(name) {
this.name = name;
}
public speak() {
console.log(`${this.name} makes a sound.`);
}
}
const dog = new Animal("Dog");
dog.speak(); // "Dog makes a sound."
console.log(dog.name); // "Dog"
この例では、name
フィールドとspeak
メソッドがpublic
として宣言されており、クラス外部からアクセスできます。
private
private
アクセス指定子は、クラスのフィールドやメソッドがクラス内部でのみアクセス可能であることを示します。private
フィールドはクラス外部から直接アクセスすることはできません。
class Car {
#brand;
constructor(brand) {
this.#brand = brand;
}
getBrand() {
return this.#brand;
}
}
const myCar = new Car("Toyota");
console.log(myCar.getBrand()); // "Toyota"
console.log(myCar.#brand); // エラー: Private field '#brand' must be declared in an enclosing class
この例では、#brand
フィールドがprivate
として宣言されており、クラス外部から直接アクセスするとエラーが発生します。
protected
protected
アクセス指定子は、クラスとそのサブクラス内でのみアクセス可能なフィールドやメソッドを示します。JavaScriptでは直接的なprotected
キーワードは存在しませんが、#
記号を使って定義されたprivate
フィールドをサブクラスでアクセスできるようにするための設計を工夫することができます。
class Employee {
#id;
constructor(id) {
this.#id = id;
}
getId() {
return this.#id;
}
}
class Manager extends Employee {
constructor(id) {
super(id);
}
displayId() {
console.log(`Manager ID: ${this.getId()}`);
}
}
const manager = new Manager(123);
manager.displayId(); // "Manager ID: 123"
この例では、Employee
クラスの#id
フィールドはprivate
ですが、getId
メソッドを通じてサブクラスManager
からアクセスできます。protected
の概念を実現するためにメソッドを活用しています。
アクセス指定子を適切に使用することで、クラスの設計がより堅牢になり、コードの保守性と安全性が向上します。次に、それぞれのアクセス指定子の具体的な使用方法を詳しく見ていきましょう。
publicフィールドの使い方
public
フィールドは、クラスの外部から直接アクセス可能なフィールドです。JavaScriptでは、クラスフィールドはデフォルトでpublic
と見なされます。これにより、外部のコードからフィールドにアクセスしたり、値を変更したりすることができます。
publicフィールドの定義方法
public
フィールドはクラスの内部で直接宣言し、クラスの外部からアクセス可能にします。
class Person {
name;
age;
constructor(name, age) {
this.name = name;
this.age = age;
}
introduce() {
console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old.`);
}
}
const alice = new Person("Alice", 30);
console.log(alice.name); // "Alice"
console.log(alice.age); // 30
alice.introduce(); // "Hi, I'm Alice and I'm 30 years old."
この例では、name
とage
フィールドがpublic
として定義されており、インスタンスalice
を通じてクラス外部からアクセスできます。
publicフィールドの使用例
public
フィールドは、オブジェクトの状態を外部から直接取得したり、設定したりする場面で使用されます。例えば、次のようにしてPerson
クラスのインスタンスのプロパティにアクセスし、変更することができます。
class Book {
title;
author;
constructor(title, author) {
this.title = title;
this.author = author;
}
getDetails() {
return `${this.title} by ${this.author}`;
}
}
const book1 = new Book("1984", "George Orwell");
console.log(book1.title); // "1984"
console.log(book1.author); // "George Orwell"
console.log(book1.getDetails()); // "1984 by George Orwell"
// publicフィールドの値を変更
book1.title = "Animal Farm";
book1.author = "George Orwell";
console.log(book1.getDetails()); // "Animal Farm by George Orwell"
この例では、Book
クラスのtitle
とauthor
フィールドがpublic
として定義されており、インスタンスbook1
を通じてクラス外部からアクセスし、値を変更することができます。
public
フィールドの使用は、クラスのインターフェースを単純化し、外部からのアクセスを容易にします。しかし、必要に応じてアクセス制限をかけることで、オブジェクトの内部状態を保護することも重要です。次に、private
フィールドの使い方について詳しく見ていきましょう。
privateフィールドの使い方
private
フィールドは、クラスの外部からアクセスできないフィールドです。JavaScriptでは、#
記号を使ってprivate
フィールドを定義します。これにより、クラス内部からのみアクセスでき、外部からの不正な操作を防ぎます。
privateフィールドの定義方法
private
フィールドは、クラス内部で#
記号を使って宣言します。クラス外部からは直接アクセスできません。
class Person {
#name;
#age;
constructor(name, age) {
this.#name = name;
this.#age = age;
}
introduce() {
console.log(`Hi, I'm ${this.#name} and I'm ${this.#age} years old.`);
}
getName() {
return this.#name;
}
getAge() {
return this.#age;
}
}
const alice = new Person("Alice", 30);
console.log(alice.getName()); // "Alice"
console.log(alice.getAge()); // 30
alice.introduce(); // "Hi, I'm Alice and I'm 30 years old."
console.log(alice.#name); // エラー: Private field '#name' must be declared in an enclosing class
この例では、#name
と#age
フィールドがprivate
として定義されており、クラス外部から直接アクセスするとエラーが発生します。
privateフィールドの使用例
private
フィールドは、クラスの内部状態を外部から保護するために使用されます。これにより、データの整合性と安全性が確保されます。
class BankAccount {
#balance;
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
console.log(`Deposited: $${amount}`);
} else {
console.log('Deposit amount must be positive');
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
console.log(`Withdrew: $${amount}`);
} else {
console.log('Invalid withdrawal amount');
}
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(1000);
account.deposit(500); // "Deposited: $500"
account.withdraw(300); // "Withdrew: $300"
console.log(account.getBalance()); // 1200
console.log(account.#balance); // エラー: Private field '#balance' must be declared in an enclosing class
この例では、BankAccount
クラスの#balance
フィールドがprivate
として定義されており、クラス外部から直接アクセスすることはできません。deposit
やwithdraw
メソッドを通じてのみ、#balance
フィールドに対する操作が行われます。
private
フィールドを使用することで、クラスの内部状態を保護し、外部からの不正なアクセスや操作を防ぐことができます。次に、protected
フィールドの使い方について詳しく見ていきましょう。
protectedフィールドの使い方
protected
フィールドは、クラスとそのサブクラス内でのみアクセス可能なフィールドです。JavaScriptには直接のprotected
キーワードはありませんが、設計によって同様の機能を実現できます。これにより、継承関係にあるクラス間でのみデータを共有し、外部からはアクセスできないようにします。
protectedフィールドの定義方法
JavaScriptではprotected
フィールドを直接定義することはできませんが、private
フィールドとメソッドを組み合わせることで、protected
フィールドに相当する動作を実現できます。
class Employee {
#name;
#age;
constructor(name, age) {
this.#name = name;
this.#age = age;
}
getDetails() {
return `${this.#name}, ${this.#age} years old`;
}
protectedMethod() {
return `Protected access to ${this.#name}`;
}
}
class Manager extends Employee {
constructor(name, age, department) {
super(name, age);
this.department = department;
}
getManagerDetails() {
return `${super.getDetails()}, Department: ${this.department}`;
}
accessProtectedMethod() {
return this.protectedMethod();
}
}
const manager = new Manager("Alice", 40, "HR");
console.log(manager.getManagerDetails()); // "Alice, 40 years old, Department: HR"
console.log(manager.accessProtectedMethod()); // "Protected access to Alice"
この例では、Employee
クラスの#name
と#age
フィールドはprivate
として定義されていますが、protectedMethod
メソッドを通じてサブクラスManager
内でアクセスされています。Manager
クラスはEmployee
クラスを継承し、protectedMethod
メソッドを利用してprotected
のような動作を実現しています。
protectedフィールドの使用例
protected
フィールドは、サブクラス内で共有しつつ、クラスの外部からはアクセスを制限する場面で使用されます。
class Vehicle {
#speed;
constructor(speed) {
this.#speed = speed;
}
getSpeed() {
return this.#speed;
}
protectedSpeedMethod() {
return `Protected speed: ${this.#speed}`;
}
}
class Car extends Vehicle {
constructor(speed, model) {
super(speed);
this.model = model;
}
getCarDetails() {
return `Model: ${this.model}, Speed: ${super.getSpeed()}`;
}
accessProtectedSpeed() {
return this.protectedSpeedMethod();
}
}
const myCar = new Car(120, "Toyota");
console.log(myCar.getCarDetails()); // "Model: Toyota, Speed: 120"
console.log(myCar.accessProtectedSpeed()); // "Protected speed: 120"
この例では、Vehicle
クラスの#speed
フィールドはprivate
として定義されていますが、protectedSpeedMethod
メソッドを通じてサブクラスCar
内でアクセスされています。これにより、protected
のような動作を実現しています。
protected
フィールドの設計は、クラス間のデータ共有と外部からのデータ保護を両立させるために重要です。次に、アクセス指定子の応用例について詳しく見ていきましょう。
アクセス指定子の応用例
アクセス指定子を適切に使用することで、コードの可読性と保守性を高め、データの安全性を確保できます。ここでは、実際の開発でのアクセス指定子の使用例とその利点を紹介します。
銀行システムにおけるアクセス指定子の使用例
銀行システムでは、顧客情報や取引情報を適切に管理し、外部からの不正なアクセスを防ぐことが重要です。以下に、public
、private
、protected
アクセス指定子を使用したクラス設計の例を示します。
class BankAccount {
#balance;
#accountNumber;
protected ownerName;
constructor(ownerName, accountNumber, initialBalance) {
this.ownerName = ownerName;
this.#accountNumber = accountNumber;
this.#balance = initialBalance;
}
public deposit(amount) {
if (amount > 0) {
this.#balance += amount;
console.log(`Deposited: $${amount}`);
} else {
console.log('Deposit amount must be positive');
}
}
public withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
console.log(`Withdrew: $${amount}`);
} else {
console.log('Invalid withdrawal amount');
}
}
public getBalance() {
return this.#balance;
}
public getAccountNumber() {
return this.#accountNumber;
}
protected getOwnerName() {
return this.ownerName;
}
}
class SavingsAccount extends BankAccount {
constructor(ownerName, accountNumber, initialBalance, interestRate) {
super(ownerName, accountNumber, initialBalance);
this.interestRate = interestRate;
}
public addInterest() {
const interest = this.getBalance() * this.interestRate / 100;
this.deposit(interest);
console.log(`Interest added: $${interest}`);
}
public displayOwner() {
console.log(`Account Owner: ${this.getOwnerName()}`);
}
}
const mySavings = new SavingsAccount("John Doe", "123456789", 1000, 2);
mySavings.deposit(500); // "Deposited: $500"
mySavings.addInterest(); // "Interest added: $30"
console.log(mySavings.getBalance()); // 1530
mySavings.displayOwner(); // "Account Owner: John Doe"
console.log(mySavings.getAccountNumber()); // "123456789"
console.log(mySavings.#balance); // エラー: Private field '#balance' must be declared in an enclosing class
この例では、BankAccount
クラスに#balance
と#accountNumber
というprivate
フィールド、ownerName
というprotected
フィールドが定義されています。これにより、クラス外部からの直接アクセスを防ぎつつ、必要に応じてサブクラスSavingsAccount
内でアクセスできるようにしています。
アクセス指定子の利点
アクセス指定子を適切に使用することで、以下の利点が得られます。
- データのカプセル化:クラスの内部状態を外部から隠蔽することで、データの不整合や不正アクセスを防ぎます。
- コードの可読性:アクセス制御を明示することで、クラスの設計意図が明確になり、コードの可読性が向上します。
- 保守性の向上:クラス内部の変更が外部に影響を与えにくくなるため、コードの保守が容易になります。
- 再利用性の向上:適切にアクセス制御されたクラスは、他のプロジェクトやコンテキストでも再利用しやすくなります。
アクセス指定子を効果的に活用することで、堅牢で保守性の高いコードを実現できます。次に、アクセス指定子とメソッドの関係について詳しく見ていきましょう。
アクセス指定子とメソッドの関係
アクセス指定子は、クラス内のメソッドにも適用できます。これにより、メソッドがどの範囲でアクセス可能かを制御し、クラスの設計をより堅牢にします。ここでは、public
、private
、protected
メソッドの使い方とその関係について説明します。
publicメソッドの使い方
public
メソッドは、クラスの外部から呼び出すことができるメソッドです。一般的に、クラスのインターフェースを提供し、外部とのやり取りを行うために使用されます。
class User {
#username;
#password;
constructor(username, password) {
this.#username = username;
this.#password = password;
}
public getUsername() {
return this.#username;
}
public setUsername(newUsername) {
this.#username = newUsername;
}
public checkPassword(password) {
return this.#password === password;
}
}
const user = new User("john_doe", "securePass123");
console.log(user.getUsername()); // "john_doe"
user.setUsername("jane_doe");
console.log(user.getUsername()); // "jane_doe"
console.log(user.checkPassword("securePass123")); // true
この例では、getUsername
、setUsername
、checkPassword
の各メソッドがpublic
として定義されており、クラス外部から呼び出すことができます。
privateメソッドの使い方
private
メソッドは、クラスの外部から直接呼び出すことができず、クラス内部でのみ使用されます。内部ロジックやヘルパーメソッドを隠蔽するために使用します。
class PasswordManager {
#password;
constructor(password) {
this.#password = password;
}
public updatePassword(newPassword) {
if (this.#validatePassword(newPassword)) {
this.#password = newPassword;
console.log("Password updated successfully.");
} else {
console.log("Password update failed. Invalid password.");
}
}
#validatePassword(password) {
return password.length >= 8;
}
}
const manager = new PasswordManager("initialPass");
manager.updatePassword("newSecurePass123"); // "Password updated successfully."
manager.updatePassword("short"); // "Password update failed. Invalid password."
console.log(manager.#validatePassword("test")); // エラー: Private field '#validatePassword' must be declared in an enclosing class
この例では、#validatePassword
メソッドがprivate
として定義されており、クラス外部から直接呼び出すことはできません。updatePassword
メソッド内でのみ使用されています。
protectedメソッドの使い方
JavaScriptには直接的なprotected
キーワードはありませんが、設計によってprotected
のような機能を持つメソッドを実現できます。サブクラスでのみアクセス可能にするためには、親クラスでprivate
メソッドを使用し、それを呼び出すpublic
またはprotected
メソッドを定義します。
class Employee {
#name;
#position;
constructor(name, position) {
this.#name = name;
this.#position = position;
}
public getDetails() {
return `${this.#name} is a ${this.#position}`;
}
#getPosition() {
return this.#position;
}
protected accessPosition() {
return this.#getPosition();
}
}
class Manager extends Employee {
constructor(name, position, department) {
super(name, position);
this.department = department;
}
public getManagerDetails() {
return `${super.getDetails()}, Department: ${this.department}`;
}
public displayPosition() {
console.log(`Position: ${this.accessPosition()}`);
}
}
const manager = new Manager("Alice", "Manager", "HR");
console.log(manager.getManagerDetails()); // "Alice is a Manager, Department: HR"
manager.displayPosition(); // "Position: Manager"
この例では、Employee
クラスの#getPosition
メソッドがprivate
として定義され、accessPosition
メソッドを通じてサブクラスManager
内でアクセスされています。
アクセス指定子をメソッドに適用することで、クラス設計がより堅牢になり、内部ロジックを保護しつつ、必要なインターフェースを提供できます。次に、アクセス指定子を使用した場合のエラーハンドリングの方法について見ていきましょう。
エラーハンドリングとアクセス指定子
アクセス指定子を使用することで、クラスのフィールドやメソッドのアクセス範囲を制御できますが、これによって発生する可能性のあるエラーや例外を適切に処理することも重要です。ここでは、アクセス指定子を使用した場合のエラーハンドリングの方法について説明します。
アクセス制限によるエラーの例
アクセス指定子を適切に使用しないと、外部からの不正なアクセスが原因でエラーが発生する可能性があります。以下の例では、private
フィールドへの直接アクセスがエラーを引き起こす様子を示しています。
class SecureDocument {
#content;
constructor(content) {
this.#content = content;
}
public getContent() {
return this.#content;
}
}
const document = new SecureDocument("Confidential");
console.log(document.getContent()); // "Confidential"
console.log(document.#content); // エラー: Private field '#content' must be declared in an enclosing class
この例では、#content
フィールドに直接アクセスしようとするとエラーが発生します。このようなエラーを回避するために、アクセス指定子を適切に使用し、外部からの直接アクセスを防ぐ必要があります。
メソッド内でのエラーハンドリング
メソッド内でアクセス指定子を使用する場合、適切なエラーハンドリングを実装して、エラーが発生した際に適切に対処できるようにします。
class BankAccount {
#balance;
constructor(initialBalance) {
this.#balance = initialBalance;
}
public deposit(amount) {
try {
if (amount <= 0) {
throw new Error("Deposit amount must be positive");
}
this.#balance += amount;
console.log(`Deposited: $${amount}`);
} catch (error) {
console.error(error.message);
}
}
public withdraw(amount) {
try {
if (amount <= 0) {
throw new Error("Withdrawal amount must be positive");
}
if (amount > this.#balance) {
throw new Error("Insufficient funds");
}
this.#balance -= amount;
console.log(`Withdrew: $${amount}`);
} catch (error) {
console.error(error.message);
}
}
public getBalance() {
return this.#balance;
}
}
const account = new BankAccount(1000);
account.deposit(500); // "Deposited: $500"
account.withdraw(2000); // "Insufficient funds"
account.withdraw(-100); // "Withdrawal amount must be positive"
console.log(account.getBalance()); // 1500
この例では、deposit
およびwithdraw
メソッド内でエラーハンドリングを実装し、無効な操作が発生した場合にエラーメッセージを表示しています。
サブクラスでのエラーハンドリング
アクセス指定子を使用したサブクラスでのエラーハンドリングも重要です。親クラスのメソッドをオーバーライドする際に適切なエラーハンドリングを実装することで、クラス間の一貫性を保ちます。
class Employee {
#name;
constructor(name) {
this.#name = name;
}
protected getName() {
return this.#name;
}
public displayInfo() {
console.log(`Employee Name: ${this.#name}`);
}
}
class Manager extends Employee {
constructor(name, department) {
super(name);
this.department = department;
}
public displayInfo() {
try {
if (!this.department) {
throw new Error("Department is required for Manager");
}
super.displayInfo();
console.log(`Department: ${this.department}`);
} catch (error) {
console.error(error.message);
}
}
}
const manager = new Manager("Alice", "HR");
manager.displayInfo(); // "Employee Name: Alice" "Department: HR"
const faultyManager = new Manager("Bob");
faultyManager.displayInfo(); // "Department is required for Manager"
この例では、Manager
クラスが親クラスEmployee
のdisplayInfo
メソッドをオーバーライドし、追加のエラーチェックを実装しています。これにより、サブクラスでのエラーハンドリングが強化されています。
アクセス指定子を使用した場合のエラーハンドリングを適切に実装することで、クラス設計の安全性と堅牢性が向上します。次に、アクセス指定子を使ったクラス設計の演習問題について見ていきましょう。
演習問題:アクセス指定子を使ったクラス設計
ここでは、アクセス指定子を使ったクラス設計の演習問題を提供します。これにより、アクセス指定子の理解を深め、実際のプログラミングでの応用力を高めることができます。
演習問題 1: ライブラリ管理システム
以下の要件を満たすクラス設計を行ってください。
Book
クラスを作成し、title
(本のタイトル)、author
(著者)、および#copies
(蔵書数)というフィールドを持たせます。Library
クラスを作成し、複数のBook
オブジェクトを管理できるようにします。Library
クラスには、次のメソッドを持たせます:
addBook(book)
: 新しい本を追加する。borrowBook(title)
: 指定されたタイトルの本を1冊借りる。蔵書数が1冊以上ある場合のみ借りられる。returnBook(title)
: 指定されたタイトルの本を1冊返す。listBooks()
: ライブラリにある全ての本のタイトルと著者を表示する。
class Book {
#copies;
constructor(title, author, copies) {
this.title = title;
this.author = author;
this.#copies = copies;
}
getDetails() {
return `${this.title} by ${this.author}`;
}
borrow() {
if (this.#copies > 0) {
this.#copies--;
return true;
}
return false;
}
returnBook() {
this.#copies++;
}
getCopies() {
return this.#copies;
}
}
class Library {
constructor() {
this.books = [];
}
addBook(book) {
this.books.push(book);
}
borrowBook(title) {
const book = this.books.find(book => book.title === title);
if (book && book.borrow()) {
console.log(`Borrowed: ${book.getDetails()}`);
} else {
console.log(`Sorry, ${title} is not available.`);
}
}
returnBook(title) {
const book = this.books.find(book => book.title === title);
if (book) {
book.returnBook();
console.log(`Returned: ${book.getDetails()}`);
} else {
console.log(`${title} not found in the library.`);
}
}
listBooks() {
this.books.forEach(book => {
console.log(`${book.getDetails()} - Copies: ${book.getCopies()}`);
});
}
}
// 使用例
const library = new Library();
const book1 = new Book("The Great Gatsby", "F. Scott Fitzgerald", 3);
const book2 = new Book("1984", "George Orwell", 2);
library.addBook(book1);
library.addBook(book2);
library.listBooks();
library.borrowBook("1984");
library.borrowBook("The Great Gatsby");
library.borrowBook("The Great Gatsby");
library.borrowBook("The Great Gatsby");
library.returnBook("1984");
library.listBooks();
演習問題 2: 銀行口座管理システム
以下の要件を満たすクラス設計を行ってください。
BankAccount
クラスを作成し、#accountNumber
(口座番号)、#balance
(残高)、およびowner
(口座所有者)というフィールドを持たせます。BankAccount
クラスには、次のメソッドを持たせます:
deposit(amount)
: 指定された金額を預金する。金額は正の数でなければならない。withdraw(amount)
: 指定された金額を引き出す。残高が足りない場合は引き出しできない。getBalance()
: 残高を返す。getAccountDetails()
: 口座番号と所有者名を返す。
class BankAccount {
#accountNumber;
#balance;
owner;
constructor(accountNumber, owner, initialBalance) {
this.#accountNumber = accountNumber;
this.owner = owner;
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
console.log(`Deposited: $${amount}`);
} else {
console.log('Deposit amount must be positive');
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
console.log(`Withdrew: $${amount}`);
} else {
console.log('Invalid withdrawal amount');
}
}
getBalance() {
return this.#balance;
}
getAccountDetails() {
return `Account Number: ${this.#accountNumber}, Owner: ${this.owner}`;
}
}
// 使用例
const account = new BankAccount("123456789", "Alice", 1000);
console.log(account.getAccountDetails());
account.deposit(500);
account.withdraw(300);
console.log(`Balance: $${account.getBalance()}`);
account.withdraw(1500); // Invalid withdrawal amount
console.log(`Balance: $${account.getBalance()}`);
これらの演習問題を通じて、アクセス指定子の使い方とその効果を実際に体験し、クラス設計のスキルを向上させてください。次に、アクセス指定子のベストプラクティスについて見ていきましょう。
アクセス指定子のベストプラクティス
アクセス指定子を適切に使用することで、クラスの設計がより安全で保守しやすくなります。ここでは、アクセス指定子を効果的に活用するためのベストプラクティスを紹介します。
1. デフォルトはprivateに
フィールドやメソッドのアクセス制御を設計する際、デフォルトでprivate
に設定することを検討してください。必要に応じてpublic
やprotected
に変更する方針を取ることで、意図しないアクセスを防ぎやすくなります。
class User {
#username;
#password;
constructor(username, password) {
this.#username = username;
this.#password = password;
}
public getUsername() {
return this.#username;
}
public checkPassword(password) {
return this.#password === password;
}
}
2. カプセル化を徹底する
クラスの内部状態を外部から隠蔽し、カプセル化を徹底します。これにより、内部実装の変更が外部に影響を与えにくくなり、コードの保守性が向上します。
class BankAccount {
#balance;
constructor(initialBalance) {
this.#balance = initialBalance;
}
public deposit(amount) {
if (amount > 0) {
this.#balance += amount;
}
}
public withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
}
}
public getBalance() {
return this.#balance;
}
}
3. アクセス指定子を明示的に使う
クラス内の全てのフィールドやメソッドに対して、アクセス指定子を明示的に指定する習慣を持つことが重要です。これにより、クラスの設計意図が明確になり、他の開発者がコードを理解しやすくなります。
class Employee {
#name;
#position;
constructor(name, position) {
this.#name = name;
this.#position = position;
}
public getDetails() {
return `${this.#name} is a ${this.#position}`;
}
}
4. 必要に応じてprotectedを使う
継承関係にあるクラス間でフィールドやメソッドを共有する必要がある場合は、protected
を使用してアクセスを制限します。ただし、protected
の使用は最小限にとどめ、できるだけprivate
を使用するように心がけます。
class Employee {
#name;
constructor(name) {
this.#name = name;
}
protected getName() {
return this.#name;
}
}
class Manager extends Employee {
constructor(name, department) {
super(name);
this.department = department;
}
public getDetails() {
return `${this.getName()} is a manager in the ${this.department} department`;
}
}
5. 例外処理を組み込む
メソッド内で例外処理を適切に実装し、アクセス制御に関連するエラーをキャッチし、適切に対処するようにします。これにより、予期しない動作を防ぎ、コードの堅牢性が向上します。
class SecureFile {
#content;
constructor(content) {
this.#content = content;
}
public readContent() {
try {
if (!this.#content) {
throw new Error("No content available");
}
return this.#content;
} catch (error) {
console.error(error.message);
}
}
public writeContent(newContent) {
try {
if (!newContent) {
throw new Error("Content cannot be empty");
}
this.#content = newContent;
} catch (error) {
console.error(error.message);
}
}
}
アクセス指定子を効果的に使用することで、クラス設計の安全性と保守性が大幅に向上します。これらのベストプラクティスを参考にして、より堅牢で明確なクラス設計を行いましょう。
次に、本記事のまとめとして、アクセス指定子の重要性と使用方法を振り返ります。
まとめ
本記事では、JavaScriptのクラスフィールドとアクセス指定子の使い方について詳しく解説しました。クラスフィールドは、オブジェクトの状態を管理するための基本的な要素であり、アクセス指定子はそのフィールドやメソッドのアクセス範囲を制御するための重要な手段です。
アクセス指定子には、public
、private
、およびJavaScriptでは直接サポートされていないものの、設計により実現可能なprotected
があります。それぞれのアクセス指定子の使い方や役割を理解することで、クラス設計がより堅牢になり、コードの保守性と安全性が向上します。
さらに、アクセス指定子を活用したエラーハンドリングやベストプラクティスについても学びました。デフォルトでprivate
を使用し、必要に応じてpublic
やprotected
に変更する方針を取ることで、意図しないアクセスを防ぎ、データのカプセル化を徹底することができます。
最後に、演習問題を通じてアクセス指定子の理解を深め、実際の開発に応用できる力を養うことができました。これらの知識を活用して、より安全で保守性の高いJavaScriptプログラムを設計しましょう。
これで、JavaScriptのクラスフィールドのアクセス指定子の使い方に関する解説を終了します。次に取り組むプロジェクトで、これらの概念を活かして効果的なクラス設計を実践してみてください。
コメント