JavaScriptは、広範囲で利用されるプログラミング言語であり、特にWeb開発において強力なツールです。しかし、大規模なプロジェクトや複雑なライブラリを構築する際、コードの可読性と保守性を向上させるために、アクセス指定子の概念を活用することが重要です。アクセス指定子を使用することで、クラスの内部と外部のインターフェースを明確に区別し、データの隠蔽とカプセル化を実現できます。本記事では、JavaScriptにおけるアクセス指定子の基本概念から、その活用方法、設計パターン、導入手順、テスト方法、ベストプラクティス、そして実践演習まで、包括的に解説します。これにより、JavaScriptのコード品質を向上させ、メンテナンスしやすいライブラリを設計するための知識を習得できます。
アクセス指定子とは何か
JavaScriptにおけるアクセス指定子とは、クラスやオブジェクトのメンバー(プロパティやメソッド)の可視性を制御する機能です。アクセス指定子を使用することで、コードの一部を外部から隠蔽し、意図しない操作や変更を防ぐことができます。
アクセス指定子の種類
JavaScriptには、以下のようなアクセス指定子があります:
public
public
アクセス指定子は、クラスの外部からでもアクセス可能なメンバーを示します。JavaScriptでは、明示的に指定しない場合、デフォルトでpublic
となります。
private
private
アクセス指定子は、クラスの内部からのみアクセス可能なメンバーを示します。ES2015以降、#
記号を使って宣言することで、private
なメンバーを定義できます。
protected
protected
アクセス指定子は、クラスとそのサブクラスからアクセス可能なメンバーを示します。JavaScriptの標準仕様にはprotected
が存在しませんが、TypeScriptやBabelなどのトランスパイラを使用することで利用可能です。
アクセス指定子の役割
アクセス指定子を使うことで、以下のようなメリットがあります:
- データ隠蔽:内部の実装詳細を隠すことで、コードの変更が外部に影響を与えにくくなります。
- カプセル化:データとそれに関連するメソッドを一つの単位にまとめることで、コードの可読性と再利用性が向上します。
- 保守性の向上:意図しない変更や誤用を防ぐことで、バグの発生を減らし、コードの保守が容易になります。
このように、アクセス指定子は、JavaScriptのライブラリ設計において重要な役割を果たします。次のセクションでは、具体的なアクセス指定子の種類とその使用方法について詳しく説明します。
アクセス指定子の種類と使用方法
JavaScriptにおけるアクセス指定子の種類と、それぞれの使用方法について詳しく見ていきます。
public
public
アクセス指定子は、クラスのメンバーがどこからでもアクセス可能であることを示します。JavaScriptでは、メンバーを宣言する際に特に何も指定しなければ、それはデフォルトでpublic
となります。
class MyClass {
constructor() {
this.publicProperty = 'This is public';
}
publicMethod() {
console.log(this.publicProperty);
}
}
const myInstance = new MyClass();
console.log(myInstance.publicProperty); // 'This is public'
myInstance.publicMethod(); // 'This is public'
private
private
アクセス指定子は、クラスの内部からのみアクセス可能なメンバーを定義します。ES2015以降、#
記号を使用してプライベートメンバーを宣言できます。
class MyClass {
#privateProperty;
constructor() {
this.#privateProperty = 'This is private';
}
#privateMethod() {
console.log(this.#privateProperty);
}
publicMethod() {
this.#privateMethod();
}
}
const myInstance = new MyClass();
console.log(myInstance.#privateProperty); // SyntaxError: Private field '#privateProperty' must be declared in an enclosing class
myInstance.publicMethod(); // 'This is private'
protected
JavaScriptの標準仕様にはprotected
アクセス指定子は存在しませんが、TypeScriptやBabelを使用することで利用可能です。protected
アクセス指定子は、クラスとそのサブクラスからアクセス可能なメンバーを定義します。
class BaseClass {
protected protectedProperty: string;
constructor() {
this.protectedProperty = 'This is protected';
}
}
class SubClass extends BaseClass {
publicMethod() {
console.log(this.protectedProperty);
}
}
const myInstance = new SubClass();
console.log(myInstance.protectedProperty); // Error: Property 'protectedProperty' is protected and only accessible within class 'BaseClass' and its subclasses.
myInstance.publicMethod(); // 'This is protected'
アクセス指定子の使い分け
アクセス指定子を適切に使い分けることで、コードのセキュリティと保守性が向上します。
public
:外部からアクセス可能にする必要があるメンバーに使用します。private
:外部に公開する必要がない内部の詳細に使用します。protected
:サブクラスからアクセスする必要があるメンバーに使用します(TypeScriptやBabel使用時)。
これらのアクセス指定子を駆使して、クラスやライブラリの設計をより堅牢で保守しやすいものにすることができます。次のセクションでは、アクセス指定子を使うことの具体的なメリットについて説明します。
アクセス指定子のメリット
JavaScriptにおいてアクセス指定子を活用することで、コードの品質と開発効率を大幅に向上させることができます。ここでは、アクセス指定子を使用する具体的なメリットについて詳しく説明します。
データ隠蔽
アクセス指定子を使用することで、クラスの内部状態を隠蔽し、外部からの不正アクセスや誤った操作を防ぐことができます。これにより、コードのセキュリティが向上します。
class BankAccount {
#balance;
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
}
}
getBalance() {
return this.#balance;
}
}
const myAccount = new BankAccount(1000);
myAccount.deposit(500);
console.log(myAccount.getBalance()); // 1500
console.log(myAccount.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class
カプセル化
アクセス指定子を使用してデータとその操作を一つのクラスにまとめることで、コードのカプセル化を実現できます。これにより、メンテナンス性が向上し、コードの一部を変更しても他の部分に影響を与えにくくなります。
class User {
#password;
constructor(name, password) {
this.name = name;
this.#password = password;
}
authenticate(password) {
return this.#password === password;
}
}
const user = new User('Alice', 'securepassword123');
console.log(user.authenticate('securepassword123')); // true
console.log(user.authenticate('wrongpassword')); // false
console.log(user.#password); // SyntaxError: Private field '#password' must be declared in an enclosing class
コードの可読性向上
アクセス指定子を明確にすることで、コードの可読性が向上します。開発者は、どのメンバーが公開されていて、どのメンバーが内部で使用されるのかを簡単に把握することができます。
class Car {
#engineStatus;
constructor() {
this.#engineStatus = 'off';
}
startEngine() {
this.#engineStatus = 'on';
console.log('Engine started');
}
stopEngine() {
this.#engineStatus = 'off';
console.log('Engine stopped');
}
getEngineStatus() {
return this.#engineStatus;
}
}
const myCar = new Car();
myCar.startEngine(); // Engine started
console.log(myCar.getEngineStatus()); // on
myCar.stopEngine(); // Engine stopped
console.log(myCar.getEngineStatus()); // off
意図しない操作の防止
アクセス指定子を使用することで、意図しない操作や変更を防止することができます。これにより、バグの発生を減らし、コードの信頼性を高めることができます。
class Safe {
#combination;
constructor(combination) {
this.#combination = combination;
}
openSafe(inputCombination) {
if (inputCombination === this.#combination) {
console.log('Safe opened');
} else {
console.log('Wrong combination');
}
}
}
const mySafe = new Safe('1234');
mySafe.openSafe('1234'); // Safe opened
mySafe.openSafe('0000'); // Wrong combination
console.log(mySafe.#combination); // SyntaxError: Private field '#combination' must be declared in an enclosing class
これらのメリットを活用することで、JavaScriptのコードがより堅牢で、保守しやすいものになります。次のセクションでは、アクセス指定子を用いた設計パターンについて詳しく説明します。
アクセス指定子を用いた設計パターン
アクセス指定子を効果的に活用することで、JavaScriptのライブラリやクラス設計の品質を向上させることができます。ここでは、具体的な設計パターンとその実装例を紹介します。
モジュールパターン
モジュールパターンは、クラスの内部でプライベートメンバーを定義し、公開するメソッドのみをエクスポートする設計パターンです。これにより、内部の実装を隠蔽し、外部からのアクセスを制限できます。
const CounterModule = (function() {
// プライベート変数
let count = 0;
// プライベートメソッド
function log() {
console.log(`Current count: ${count}`);
}
return {
// パブリックメソッド
increment() {
count++;
log();
},
reset() {
count = 0;
log();
}
};
})();
CounterModule.increment(); // Current count: 1
CounterModule.increment(); // Current count: 2
CounterModule.reset(); // Current count: 0
// CounterModule.count; // undefined (アクセス不可)
シングルトンパターン
シングルトンパターンは、クラスのインスタンスが一つしか存在しないことを保証する設計パターンです。アクセス指定子を使用して、内部状態やメソッドを隠蔽し、外部からの不正な操作を防ぎます。
class Singleton {
// シングルトンインスタンスを保持するプライベート静的プロパティ
static #instance;
// プライベートコンストラクタ
constructor(name) {
if (Singleton.#instance) {
return Singleton.#instance;
}
this.name = name;
Singleton.#instance = this;
}
// パブリックメソッド
getName() {
return this.name;
}
}
const instance1 = new Singleton('Instance 1');
console.log(instance1.getName()); // Instance 1
const instance2 = new Singleton('Instance 2');
console.log(instance2.getName()); // Instance 1
console.log(instance1 === instance2); // true
ファクトリーパターン
ファクトリーパターンは、オブジェクトの生成を専門とするメソッドを用意する設計パターンです。内部でアクセス指定子を使用することで、生成プロセスを隠蔽し、外部にシンプルなインターフェースを提供します。
class Car {
#engineStatus;
constructor(model) {
this.model = model;
this.#engineStatus = 'off';
}
startEngine() {
this.#engineStatus = 'on';
console.log(`${this.model} engine started`);
}
stopEngine() {
this.#engineStatus = 'off';
console.log(`${this.model} engine stopped`);
}
getEngineStatus() {
return this.#engineStatus;
}
}
class CarFactory {
static createCar(model) {
return new Car(model);
}
}
const myCar = CarFactory.createCar('Tesla Model 3');
myCar.startEngine(); // Tesla Model 3 engine started
console.log(myCar.getEngineStatus()); // on
myCar.stopEngine(); // Tesla Model 3 engine stopped
console.log(myCar.getEngineStatus()); // off
デコレーターパターン
デコレーターパターンは、オブジェクトの機能を動的に追加または修正する設計パターンです。アクセス指定子を使用して、元のクラスの内部状態を保護しながら、追加機能を提供します。
class SimpleCoffee {
getCost() {
return 5;
}
getDescription() {
return 'Simple coffee';
}
}
class MilkDecorator {
#coffee;
constructor(coffee) {
this.#coffee = coffee;
}
getCost() {
return this.#coffee.getCost() + 1.5;
}
getDescription() {
return `${this.#coffee.getDescription()}, milk`;
}
}
class SugarDecorator {
#coffee;
constructor(coffee) {
this.#coffee = coffee;
}
getCost() {
return this.#coffee.getCost() + 0.5;
}
getDescription() {
return `${this.#coffee.getDescription()}, sugar`;
}
}
let myCoffee = new SimpleCoffee();
console.log(myCoffee.getDescription() + " $" + myCoffee.getCost()); // Simple coffee $5
myCoffee = new MilkDecorator(myCoffee);
console.log(myCoffee.getDescription() + " $" + myCoffee.getCost()); // Simple coffee, milk $6.5
myCoffee = new SugarDecorator(myCoffee);
console.log(myCoffee.getDescription() + " $" + myCoffee.getCost()); // Simple coffee, milk, sugar $7
これらの設計パターンを通じて、アクセス指定子を効果的に活用し、堅牢で保守性の高いJavaScriptライブラリを構築することができます。次のセクションでは、プロジェクトにアクセス指定子を導入する具体的な方法について説明します。
プロジェクトにアクセス指定子を導入する方法
既存のJavaScriptプロジェクトにアクセス指定子を導入することで、コードの保守性やセキュリティを向上させることができます。ここでは、ステップバイステップで導入手順を解説します。
1. コードの分析と設計
まず、プロジェクト内のコードを分析し、どのメンバーを公開する必要があり、どのメンバーを隠蔽するべきかを決定します。この段階では、以下の点に注意します:
- 外部からアクセスが必要なメンバーは
public
として残す - 内部でのみ使用されるメンバーは
private
に変更する - 継承関係がある場合、サブクラスからアクセスが必要なメンバーは
protected
にする(TypeScript使用時)
2. プライベートメンバーの導入
次に、プライベートメンバーを導入します。JavaScriptでは、#
記号を使用してプライベートメンバーを宣言できます。
class MyClass {
#privateField;
constructor() {
this.#privateField = 'This is private';
}
publicMethod() {
console.log(this.#privateField);
}
}
3. メンバーのアクセス修正
プロジェクト内のクラスやオブジェクトのメンバーをpublic
またはprivate
に変更します。必要に応じて、TypeScriptを使用してprotected
メンバーも導入します。
class MyClass {
#privateField;
publicField;
constructor() {
this.#privateField = 'Private';
this.publicField = 'Public';
}
#privateMethod() {
console.log(this.#privateField);
}
publicMethod() {
this.#privateMethod();
console.log(this.publicField);
}
}
4. アクセス制御のテスト
アクセス指定子の変更が正しく機能することを確認するために、テストを実行します。プライベートメンバーやメソッドに外部からアクセスできないことを確認します。
const instance = new MyClass();
instance.publicMethod(); // 正常に動作することを確認
console.log(instance.publicField); // 'Public'
// console.log(instance.#privateField); // エラーが発生することを確認
// instance.#privateMethod(); // エラーが発生することを確認
5. ドキュメントの更新
アクセス指定子の導入に伴い、プロジェクトのドキュメントを更新します。公開されているメンバーと隠蔽されているメンバーのリストを明記し、コードの使用方法を記載します。
6. コードレビューとフィードバック
アクセス指定子の導入後、コードレビューを行い、他の開発者からフィードバックをもらいます。これにより、導入した変更がプロジェクト全体にどのように影響を与えるかを確認できます。
これらの手順を通じて、既存のJavaScriptプロジェクトにアクセス指定子を導入し、コードの品質と保守性を向上させることができます。次のセクションでは、アクセス指定子を使用したコードのテスト方法について詳しく説明します。
アクセス指定子を使ったライブラリのテスト
アクセス指定子を使用したJavaScriptのコードをテストすることで、コードの品質と信頼性を確保することが重要です。ここでは、プライベートメンバーやメソッドを含むクラスのテスト方法について詳しく説明します。
1. パブリックメソッドのテスト
パブリックメソッドは外部からアクセス可能であるため、通常のテストフレームワークを使用して直接テストできます。以下は、Jestを使用したパブリックメソッドのテスト例です。
// MyClass.js
class MyClass {
#privateField;
constructor() {
this.#privateField = 'This is private';
}
publicMethod() {
return this.#privateField;
}
}
module.exports = MyClass;
// MyClass.test.js
const MyClass = require('./MyClass');
test('publicMethod should return private field', () => {
const instance = new MyClass();
expect(instance.publicMethod()).toBe('This is private');
});
2. プライベートメンバーのテスト
プライベートメンバーやメソッドは外部から直接アクセスできないため、テストが難しくなります。ここでは、いくつかのアプローチを紹介します。
2.1. パブリックメソッドを通じたテスト
プライベートメンバーに依存するパブリックメソッドをテストすることで、間接的にプライベートメンバーの動作を確認できます。
class MyClass {
#privateField;
constructor() {
this.#privateField = 'This is private';
}
publicMethod() {
return this.#privateField;
}
}
// Test
test('publicMethod should return private field', () => {
const instance = new MyClass();
expect(instance.publicMethod()).toBe('This is private');
});
2.2. リフレクションを使用したテスト
リフレクションを使用してプライベートメンバーにアクセスする方法もあります。ただし、この方法は推奨されない場合があります。
class MyClass {
#privateField;
constructor() {
this.#privateField = 'This is private';
}
publicMethod() {
return this.#privateField;
}
}
// Test
test('private field can be accessed using reflection (not recommended)', () => {
const instance = new MyClass();
const privateField = instance['#privateField'];
expect(privateField).toBe('This is private');
});
2.3. プライベートメソッドを間接的にテスト
プライベートメソッドがパブリックメソッドによって呼び出される場合、そのパブリックメソッドの動作をテストすることで間接的にプライベートメソッドをテストできます。
class MyClass {
#privateField;
constructor() {
this.#privateField = 'This is private';
}
#privateMethod() {
return this.#privateField;
}
publicMethod() {
return this.#privateMethod();
}
}
// Test
test('publicMethod should call privateMethod and return its value', () => {
const instance = new MyClass();
expect(instance.publicMethod()).toBe('This is private');
});
3. モックとスタブの使用
テストを容易にするために、モックやスタブを使用して依存関係を制御します。これにより、プライベートメンバーの影響を最小限に抑えつつ、パブリックメソッドの動作を検証できます。
class MyClass {
#privateField;
constructor() {
this.#privateField = 'This is private';
}
#privateMethod() {
return this.#privateField;
}
publicMethod() {
return this.#privateMethod();
}
}
// Test with Jest
test('publicMethod should call privateMethod and return its value', () => {
const instance = new MyClass();
const spy = jest.spyOn(instance, 'publicMethod');
expect(instance.publicMethod()).toBe('This is private');
expect(spy).toHaveBeenCalled();
});
これらの方法を駆使して、アクセス指定子を使用したJavaScriptコードのテストを効果的に行うことができます。次のセクションでは、アクセス指定子を使用する際のベストプラクティスについて説明します。
アクセス指定子を使う際のベストプラクティス
アクセス指定子を効果的に使用することで、JavaScriptコードの品質と保守性を向上させることができます。以下では、アクセス指定子を使用する際のベストプラクティスを紹介します。
1. 意図的な設計
アクセス指定子を適切に設計することで、コードの明確なインターフェースを定義できます。クラスやモジュールの設計段階で、どのメンバーを公開するか、どのメンバーを隠蔽するかを慎重に決定します。
class User {
#password;
constructor(username, password) {
this.username = username;
this.#password = password;
}
authenticate(password) {
return this.#password === password;
}
}
2. パブリックAPIの明確化
クラスのパブリックメソッドやプロパティを明確に定義し、外部からの利用を意図した部分のみを公開します。これにより、APIの利用者に対して予測可能で一貫性のあるインターフェースを提供できます。
class Account {
#balance;
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
}
}
getBalance() {
return this.#balance;
}
}
3. 内部状態の隠蔽
プライベートメンバーを使用して、クラスの内部状態を隠蔽します。これにより、クラスの内部実装に依存しないコードを書くことができ、変更に強い設計が可能になります。
class Car {
#engineStatus;
constructor() {
this.#engineStatus = 'off';
}
startEngine() {
this.#engineStatus = 'on';
console.log('Engine started');
}
stopEngine() {
this.#engineStatus = 'off';
console.log('Engine stopped');
}
}
4. カプセル化の徹底
データとその操作をクラス内にカプセル化し、クラス外部から直接アクセスされないようにします。これにより、データの一貫性を保ち、バグの発生を防ぎます。
class Person {
#age;
constructor(name, age) {
this.name = name;
this.#age = age;
}
haveBirthday() {
this.#age++;
}
getAge() {
return this.#age;
}
}
5. テストのしやすさを考慮
プライベートメンバーのテストが難しい場合、必要に応じてパブリックメソッドを通じてテストを行います。また、テスト可能な設計を意識してクラスやモジュールを設計します。
class Calculator {
#add(a, b) {
return a + b;
}
addNumbers(a, b) {
return this.#add(a, b);
}
}
// テスト
test('addNumbers should return the sum of two numbers', () => {
const calculator = new Calculator();
expect(calculator.addNumbers(2, 3)).toBe(5);
});
6. ドキュメントの整備
アクセス指定子を使用したクラスやモジュールのドキュメントを整備し、どのメンバーが公開されているか、どのメンバーが内部で使用されているかを明確にします。これにより、他の開発者がコードを理解しやすくなります。
# Personクラス
## パブリックメソッド
- `haveBirthday()`: 年齢を1歳増やす
- `getAge()`: 現在の年齢を取得する
## プライベートメンバー
- `#age`: 年齢を保持するプライベートフィールド
これらのベストプラクティスを遵守することで、アクセス指定子を効果的に活用し、保守性の高いJavaScriptコードを作成することができます。次のセクションでは、アクセス指定子を使用する際のトラブルシューティングと注意点について説明します。
トラブルシューティングと注意点
アクセス指定子を使用する際には、いくつかの問題に直面することがあります。ここでは、よくあるトラブルとその解決方法、およびアクセス指定子を使用する際の注意点を紹介します。
1. プライベートメンバーへのアクセスエラー
プライベートメンバーに直接アクセスしようとすると、エラーが発生します。これにより、外部からの不正アクセスが防止されますが、開発中にこのエラーに遭遇することがあります。
class MyClass {
#privateField;
constructor() {
this.#privateField = 'This is private';
}
publicMethod() {
return this.#privateField;
}
}
const instance = new MyClass();
console.log(instance.publicMethod()); // 正常に動作する
// console.log(instance.#privateField); // SyntaxError: Private field '#privateField' must be declared in an enclosing class
解決策
プライベートメンバーにアクセスする必要がある場合は、パブリックメソッドを通じてアクセスするように設計を変更します。
2. プライベートメンバーのテストが難しい
プライベートメンバーやメソッドのテストが難しい場合があります。これは、プライベートメンバーが外部から直接アクセスできないためです。
解決策
プライベートメンバーをテストするために、パブリックメソッドを通じてその動作を確認します。また、必要に応じてテストダブル(モック、スタブなど)を使用して依存関係を制御します。
3. 継承時のアクセス制御
クラスの継承時に、プライベートメンバーにアクセスできない問題が発生することがあります。これは、プライベートメンバーがサブクラスからもアクセスできないためです。
class ParentClass {
#privateField;
constructor() {
this.#privateField = 'This is private';
}
getPrivateField() {
return this.#privateField;
}
}
class ChildClass extends ParentClass {
constructor() {
super();
}
childMethod() {
// return this.#privateField; // SyntaxError: Private field '#privateField' must be declared in an enclosing class
return this.getPrivateField(); // 親クラスのパブリックメソッドを通じてアクセス
}
}
解決策
サブクラスからアクセスが必要な場合は、親クラスにパブリックまたはプロテクテッドメソッドを追加して、間接的にアクセスできるようにします。
4. アクセス指定子のサポート
JavaScriptの標準仕様では、private
キーワードのみがサポートされています。protected
キーワードを使用する場合は、TypeScriptやBabelなどのトランスパイラを使用する必要があります。
解決策
TypeScriptやBabelを導入し、コードをトランスパイルしてprotected
アクセス指定子を使用できるようにします。
class ParentClass {
protected protectedField: string;
constructor() {
this.protectedField = 'This is protected';
}
}
class ChildClass extends ParentClass {
constructor() {
super();
console.log(this.protectedField); // サブクラスからアクセス可能
}
}
5. 依存関係の管理
アクセス指定子を使用することで、クラスの依存関係が複雑になることがあります。特に、大規模なプロジェクトでは、依存関係の管理が重要です。
解決策
依存関係を明確にし、ドキュメントを整備して、開発チーム全体で共有します。また、モジュール分割や依存性注入などの設計パターンを利用して、依存関係を管理しやすくします。
class Service {
#dependency;
constructor(dependency) {
this.#dependency = dependency;
}
performAction() {
return this.#dependency.action();
}
}
class Dependency {
action() {
return 'Action performed';
}
}
const dependency = new Dependency();
const service = new Service(dependency);
console.log(service.performAction()); // 'Action performed'
これらのトラブルシューティングと注意点を参考にして、アクセス指定子を使用したJavaScriptコードをより効果的に開発、保守することができます。次のセクションでは、具体的な演習問題を通じて、アクセス指定子を使ったライブラリの設計と実装を学びます。
実践演習:ライブラリの設計と実装
アクセス指定子を活用して、実際にJavaScriptのライブラリを設計・実装する演習を行います。この演習では、アクセス指定子の使い方を理解し、効果的なコード設計を体験していただきます。
課題1: ユーザー管理システムの設計
ユーザー情報を管理するシステムを設計し、ユーザーのパスワードをプライベートに保護します。以下の手順に従って、コードを実装してください。
ステップ1: クラスの定義
User
クラスを定義し、ユーザー名とパスワードを管理します。パスワードはプライベートに設定します。
class User {
#password;
constructor(username, password) {
this.username = username;
this.#password = password;
}
authenticate(inputPassword) {
return this.#password === inputPassword;
}
changePassword(oldPassword, newPassword) {
if (this.#password === oldPassword) {
this.#password = newPassword;
return true;
} else {
return false;
}
}
}
// テスト
const user = new User('john_doe', 'securePassword');
console.log(user.authenticate('securePassword')); // true
console.log(user.authenticate('wrongPassword')); // false
console.log(user.changePassword('securePassword', 'newPassword')); // true
console.log(user.authenticate('newPassword')); // true
課題2: 銀行口座管理システムの設計
銀行口座の残高を管理し、預金と引き出しの機能を実装します。残高はプライベートに保護します。
ステップ1: クラスの定義
BankAccount
クラスを定義し、残高をプライベートに設定します。
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
account.withdraw(2000); // 引き出しできない
console.log(account.getBalance()); // 1300
課題3: ライブラリのモジュール化
上記のユーザー管理システムと銀行口座管理システムをモジュールとして分割し、インポートして使用します。
ステップ1: モジュールの定義
それぞれのクラスを別々のファイルに分割し、エクスポートします。
// User.js
class User {
#password;
constructor(username, password) {
this.username = username;
this.#password = password;
}
authenticate(inputPassword) {
return this.#password === inputPassword;
}
changePassword(oldPassword, newPassword) {
if (this.#password === oldPassword) {
this.#password = newPassword;
return true;
} else {
return false;
}
}
}
module.exports = User;
// BankAccount.js
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;
}
}
module.exports = BankAccount;
ステップ2: モジュールの使用
エクスポートしたクラスを別のファイルでインポートして使用します。
// app.js
const User = require('./User');
const BankAccount = require('./BankAccount');
const user = new User('jane_doe', 'initialPassword');
console.log(user.authenticate('initialPassword')); // true
user.changePassword('initialPassword', 'newSecurePassword');
console.log(user.authenticate('newSecurePassword')); // true
const account = new BankAccount(2000);
account.deposit(500);
console.log(account.getBalance()); // 2500
account.withdraw(1500);
console.log(account.getBalance()); // 1000
課題4: テストの実装
上記のモジュールに対するユニットテストをJestを使用して実装します。
// User.test.js
const User = require('./User');
test('User authentication and password change', () => {
const user = new User('test_user', 'testPassword');
expect(user.authenticate('testPassword')).toBe(true);
expect(user.changePassword('testPassword', 'newTestPassword')).toBe(true);
expect(user.authenticate('newTestPassword')).toBe(true);
expect(user.authenticate('testPassword')).toBe(false);
});
// BankAccount.test.js
const BankAccount = require('./BankAccount');
test('BankAccount deposit and withdraw', () => {
const account = new BankAccount(1000);
account.deposit(500);
expect(account.getBalance()).toBe(1500);
account.withdraw(200);
expect(account.getBalance()).toBe(1300);
account.withdraw(2000);
expect(account.getBalance()).toBe(1300);
});
これらの演習を通じて、アクセス指定子を使ったライブラリの設計と実装について学びました。次のセクションでは、この記事全体のまとめを行います。
まとめ
本記事では、JavaScriptにおけるアクセス指定子を使ったライブラリ設計の重要性と具体的な方法について詳しく解説しました。アクセス指定子を適切に使用することで、コードのセキュリティ、保守性、可読性を向上させることができます。
まず、アクセス指定子の基本概念と種類について説明し、public
、private
、およびprotected
の使い方と役割を理解しました。次に、アクセス指定子を活用した設計パターンを学び、具体的な例を通じてその効果を確認しました。
さらに、アクセス指定子を既存のプロジェクトに導入する方法や、アクセス指定子を使用したコードのテスト方法についても詳しく説明しました。特に、プライベートメンバーのテストに関する課題とその解決策について学びました。
ベストプラクティスのセクションでは、効果的にアクセス指定子を利用するための設計のポイントや、注意点を紹介しました。これにより、アクセス指定子を使ったコードの設計とメンテナンスが容易になるでしょう。
最後に、実践演習を通じて、アクセス指定子を使ったライブラリの設計と実装のスキルを磨きました。ユーザー管理システムや銀行口座管理システムの例を通じて、アクセス指定子を使用したコードの具体的な実装方法を理解しました。
これらの知識とスキルを活用して、JavaScriptプロジェクトの品質を向上させ、より堅牢で保守しやすいライブラリを設計することができるようになります。
コメント