TypeScriptでオブジェクト指向プログラミングを行う際、クラスのフィールドやメソッドへのアクセス制御は非常に重要です。この制御を適切に行うことで、クラスの設計を堅牢にし、意図しない操作や不正なアクセスを防ぐことができます。TypeScriptは、クラスのフィールドやメソッドに対してアクセス指定子(public
、private
、protected
)を使用することで、アクセス範囲を明確に定義できる特徴を持っています。
本記事では、TypeScriptにおけるアクセス指定子を用いたクラス設計の方法について解説し、設計の堅牢性を高めるためのベストプラクティスを紹介します。
TypeScriptにおけるアクセス指定子の概要
TypeScriptでは、クラスのメンバー(フィールドやメソッド)に対してアクセス制御を行うために、アクセス指定子を使用します。これにより、クラス外からアクセス可能な範囲を制限することができ、クラス設計のセキュリティや堅牢性が向上します。
TypeScriptで使用できる主なアクセス指定子は以下の3つです:
public
public
指定子は、クラスのメンバーを外部から自由にアクセスできることを意味します。アクセス指定子を指定しない場合、デフォルトでpublic
が適用されます。
private
private
指定子は、そのクラス内でのみメンバーへのアクセスを許可します。他のクラスやインスタンスからは直接アクセスできません。
protected
protected
指定子は、クラス自身およびそのサブクラスからのみアクセス可能です。サブクラスでの継承を前提としたアクセス制御を行いたい場合に有効です。
これらの指定子を使うことで、クラスの設計をより堅牢に保つことが可能です。
public、private、protectedの違い
TypeScriptでクラスを設計する際、public
、private
、protected
というアクセス指定子を使ってフィールドやメソッドのアクセス制御を行います。これらはクラスのメンバーに対するアクセス可能範囲を決定する重要な要素です。それぞれの違いについて詳しく見ていきましょう。
public
public
は、クラスのメンバーがどこからでもアクセス可能であることを意味します。外部のクラスやインスタンスからもアクセス可能で、デフォルトではすべてのクラスメンバーがpublic
になります。以下はその例です:
class Person {
public name: string;
constructor(name: string) {
this.name = name;
}
}
const person = new Person("John");
console.log(person.name); // "John"にアクセス可能
private
private
は、クラスの内部からしかアクセスできないことを意味します。外部からのアクセスは許可されず、同じクラス内でのみ使用可能です。これにより、フィールドをカプセル化し、データを保護できます。
class Person {
private age: number;
constructor(age: number) {
this.age = age;
}
public getAge(): number {
return this.age;
}
}
const person = new Person(30);
console.log(person.age); // エラー: 'age'はprivate
console.log(person.getAge()); // メソッド経由で取得可能
protected
protected
は、クラス自身およびそのサブクラス内でのみアクセス可能です。これは、private
と似ていますが、サブクラスでのアクセスも許可される点が異なります。継承関係にあるクラスで情報を共有する際に便利です。
class Person {
protected age: number;
constructor(age: number) {
this.age = age;
}
}
class Employee extends Person {
constructor(age: number) {
super(age);
}
public getEmployeeAge(): number {
return this.age; // protectedフィールドにアクセス可能
}
}
const employee = new Employee(25);
console.log(employee.age); // エラー: 'age'はprotected
console.log(employee.getEmployeeAge()); // サブクラス経由でアクセス可能
これら3つのアクセス指定子を理解し、適切に使い分けることで、クラスの設計をより安全かつ効果的に制御できます。
アクセス指定子を使うメリット
TypeScriptにおいてアクセス指定子を使用することは、クラス設計の堅牢性を高め、プログラム全体の安定性や保守性を向上させるために重要です。ここでは、アクセス指定子を使用する具体的なメリットをいくつか紹介します。
1. データのカプセル化
private
やprotected
のアクセス指定子を使用することで、クラス内のデータを外部から直接操作できないように制御できます。これにより、クラスの内部状態を隠蔽し、予期しない変更を防ぐことができるため、クラスが意図通りに動作することが保証されます。
class BankAccount {
private balance: number = 0;
public deposit(amount: number): void {
if (amount > 0) {
this.balance += amount;
}
}
public getBalance(): number {
return this.balance;
}
}
const account = new BankAccount();
account.deposit(100);
console.log(account.getBalance()); // balanceへの直接アクセスはできないが、メソッド経由で操作可能
2. クラス設計の柔軟性と制御力
protected
を使うことで、クラスのフィールドやメソッドを継承先で制御しやすくなります。サブクラスに適切なアクセス権限を与えつつ、外部からの不要なアクセスを制限できるため、柔軟な設計が可能になります。
3. コードの保守性と信頼性向上
アクセス指定子を適切に使用することで、コードの保守性が向上します。クラスのインターフェースが明確になるため、他の開発者や将来の自分がコードを扱いやすくなります。また、誤ったアクセスによるバグや予期しない挙動を防ぐことができ、信頼性の高いコードを維持できます。
4. 予測可能な動作
アクセス指定子を使うことで、クラスのどの部分が外部から利用されるべきかが明確になります。これにより、他の開発者がそのクラスを利用する際に、どのフィールドやメソッドを操作すればよいかが容易に判断でき、クラスの動作を予測しやすくなります。
アクセス指定子を活用することで、データの保護と意図しない操作の防止、クラスの継承や拡張の柔軟性向上といったメリットを得ることができ、結果的に堅牢で保守しやすい設計が実現します。
クラスのフィールドの保護とカプセル化
アクセス指定子は、クラスのフィールドやメソッドを外部から保護し、内部構造を隠蔽するために重要な役割を果たします。このプロセスは「カプセル化」と呼ばれ、オブジェクト指向プログラミングの基本的な概念の一つです。カプセル化により、クラス内部のデータを不正な操作から守り、予期しないバグやエラーを防ぐことができます。
カプセル化の重要性
カプセル化とは、クラス内のフィールドやメソッドを外部から隠し、アクセスを制御することを指します。これにより、クラスの使用者が内部の実装に依存することなく、公開されたメソッドを通じてクラスを操作できるようになります。フィールドを直接操作するのではなく、適切なメソッドを介して間接的に操作することで、安全性と柔軟性が向上します。
class User {
private username: string;
private password: string;
constructor(username: string, password: string) {
this.username = username;
this.password = password;
}
// 外部から操作できるメソッド
public getUsername(): string {
return this.username;
}
public setPassword(newPassword: string): void {
if (newPassword.length >= 8) {
this.password = newPassword;
} else {
console.log("パスワードは8文字以上でなければなりません。");
}
}
}
上記の例では、username
とpassword
フィールドがprivate
指定されているため、外部から直接アクセスできません。しかし、getUsername
やsetPassword
といったメソッドを介して、必要な操作を行うことができます。これにより、パスワードが直接変更されるリスクを回避し、クラス内部のデータが不正に操作されるのを防ぎます。
クラス設計の堅牢性向上
アクセス指定子を使ってクラスのフィールドを保護することで、意図しないデータ変更を防ぎ、クラスの堅牢性が大幅に向上します。たとえば、フィールドをprivate
にすることで、クラスの利用者がデータに直接アクセスして変更するのを防ぎ、プログラムの安定性が保たれます。
また、クラスの内部実装を隠蔽することで、後に内部構造を変更する際に、外部からの影響を最小限に抑えることができます。これにより、クラスを安全に拡張・修正でき、保守性が向上します。
カプセル化とアクセス指定子の使い分け
アクセス指定子を使い分けることで、クラス設計の意図を明確にし、他の開発者がクラスを使用する際にどの部分が安全に操作できるかを理解しやすくなります。例えば、フィールドはprivate
にして外部から直接アクセスできないようにし、必要な情報や操作はpublic
メソッドを通じて提供する、という形がよく採用されます。
カプセル化を適切に行うことで、クラスの内部状態を保護し、安定したコードを維持することができます。
実際のコード例:アクセス指定子を使ったクラス設計
アクセス指定子を効果的に使うことで、クラス設計を堅牢にし、外部からの不要なアクセスや誤操作を防ぐことができます。ここでは、アクセス指定子を活用したクラス設計の具体的なコード例を見ていきます。
例:銀行口座クラス
以下は、public
、private
、protected
を使用したシンプルな銀行口座クラスの例です。このクラスでは、残高や口座番号といった重要な情報はprivate
で保護し、外部から直接操作できないようにしています。また、口座に対する操作は公開されたメソッドを通じてのみ可能にしています。
class BankAccount {
private accountNumber: string;
private balance: number;
constructor(accountNumber: string, initialBalance: number) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
}
// 口座番号を取得するメソッド
public getAccountNumber(): string {
return this.accountNumber;
}
// 現在の残高を取得するメソッド
public getBalance(): number {
return this.balance;
}
// 残高を増やすメソッド
public deposit(amount: number): void {
if (amount > 0) {
this.balance += amount;
} else {
console.log("無効な金額です。");
}
}
// 残高を減らすメソッド
public withdraw(amount: number): void {
if (amount > 0 && amount <= this.balance) {
this.balance -= amount;
} else {
console.log("引き出しできません。");
}
}
}
const account = new BankAccount("123456789", 1000);
console.log(account.getAccountNumber()); // "123456789"
console.log(account.getBalance()); // 1000
account.deposit(500);
console.log(account.getBalance()); // 1500
account.withdraw(200);
console.log(account.getBalance()); // 1300
コードの解説
accountNumber
とbalance
フィールドはprivate
指定されており、クラス外からは直接アクセスできません。これにより、外部からの誤ったデータ操作や不正アクセスを防ぎます。- 口座番号を取得する
getAccountNumber
メソッドと、残高を取得するgetBalance
メソッドはpublic
として定義されているため、外部から呼び出してデータを取得できます。 deposit
とwithdraw
メソッドを使用することで、残高の操作を行います。これらのメソッドをpublic
として定義することで、外部からも合法的にクラスのフィールドに影響を与えることができます。
このように、フィールドをprivate
で保護し、必要な操作のみをpublic
メソッドを通じて行うことで、クラスの設計が堅牢で安全なものになります。
継承を使った例:従業員クラス
protected
を使ってサブクラスでの継承時にアクセス制御を行う例も紹介します。
class Person {
protected name: string;
constructor(name: string) {
this.name = name;
}
public getName(): string {
return this.name;
}
}
class Employee extends Person {
private employeeId: string;
constructor(name: string, employeeId: string) {
super(name);
this.employeeId = employeeId;
}
public getEmployeeInfo(): string {
return `Name: ${this.name}, Employee ID: ${this.employeeId}`;
}
}
const employee = new Employee("Alice", "EMP001");
console.log(employee.getEmployeeInfo()); // Name: Alice, Employee ID: EMP001
コードの解説
- 親クラス
Person
では、name
フィールドがprotected
で定義されています。これにより、サブクラスのEmployee
からname
にアクセスできるようになっていますが、外部からは直接アクセスできません。 - サブクラス
Employee
はprivate
フィールドemployeeId
を持ち、これも外部から直接アクセスできないようにしています。 getEmployeeInfo
メソッドは、name
とemployeeId
の情報をまとめて外部に提供します。
このように、アクセス指定子を効果的に使い分けることで、クラスのデータを安全に保護しつつ、必要な情報だけを外部に公開できます。
応用例:getterとsetterの活用
TypeScriptにおいて、アクセス指定子とgetter
およびsetter
を組み合わせることで、クラスのフィールドへのアクセスをさらに制御し、柔軟で安全なデータ管理が可能になります。getter
とsetter
は、フィールドへの読み書きに特別な処理を追加できるメソッドであり、直接フィールドにアクセスすることなく、その操作を管理できます。
getterとsetterの基本
getter
は、フィールドの値を取得する際に特別な処理を行うためのメソッドです。setter
は、フィールドに値を設定する際に追加のバリデーションやロジックを実行できます。これにより、直接フィールドにアクセスする代わりに、安全な方法でデータを操作できます。
以下は、getter
とsetter
を使った実例です。
class User {
private _age: number = 0;
// ageのgetter
public get age(): number {
return this._age;
}
// ageのsetter
public set age(value: number) {
if (value >= 0 && value <= 120) {
this._age = value;
} else {
console.log("年齢は0から120の範囲で設定してください。");
}
}
}
const user = new User();
user.age = 30; // setterを通じて値を設定
console.log(user.age); // getterを通じて値を取得 -> 30
user.age = -5; // エラーメッセージが表示され、値は変更されない
getterとsetterを使うメリット
1. データへの安全なアクセス
getter
とsetter
を使うことで、フィールドへの不正な操作を防ぎつつ、必要な処理を追加できます。たとえば、setter
では値のバリデーションを行い、正しい範囲の値のみがフィールドに設定されるようにできます。これにより、クラス内のデータの整合性を保つことができ、予期しないエラーやバグを防止できます。
2. フィールドの読み取り専用化
特定のフィールドに対して、外部から値を取得することだけを許可し、変更は許さない場合があります。こうした場合、getter
だけを定義して、setter
を定義しないことで、フィールドを読み取り専用にできます。
class Product {
private _price: number;
constructor(price: number) {
this._price = price;
}
public get price(): number {
return this._price;
}
}
const product = new Product(100);
console.log(product.price); // 100
product.price = 200; // エラー: setterが定義されていないため、値を変更できない
3. 内部データの抽象化
getter
とsetter
を使うことで、フィールドの内部実装を隠し、外部からはその実装に依存しない形で操作できます。これにより、フィールドの内部実装が変更されたとしても、外部からのアクセス方法を変える必要がなくなります。例えば、計算された結果をgetter
で返すことも可能です。
class Rectangle {
private width: number;
private height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
// 面積を計算して返すgetter
public get area(): number {
return this.width * this.height;
}
}
const rectangle = new Rectangle(10, 20);
console.log(rectangle.area); // 200
このように、getter
とsetter
を使うことで、フィールドの操作をより柔軟に管理し、安全で堅牢なクラス設計を実現することができます。
アクセス指定子を使用したテストとデバッグの注意点
アクセス指定子を用いてクラス設計を行う際、テストやデバッグの過程でいくつかの注意点が生じます。特に、private
やprotected
のメンバーは外部から直接アクセスできないため、テストやデバッグを行う際には特別な工夫が必要になります。ここでは、アクセス指定子を使用したコードにおけるテストとデバッグの際に考慮すべき点について解説します。
1. `private`メンバーのテスト
private
フィールドやメソッドはクラスの外部からアクセスできないため、通常の単体テストでは直接テストすることができません。そのため、private
メンバーに関しては、間接的にテストを行う方法が必要です。一般的なアプローチとして、public
メソッドを使って、private
メンバーに関係する挙動をテストします。
たとえば、private
フィールドを変更した結果をpublic
メソッドで確認する形でテストを行うことが考えられます。
class BankAccount {
private balance: number = 0;
public deposit(amount: number): void {
if (amount > 0) {
this.balance += amount;
}
}
public getBalance(): number {
return this.balance;
}
}
// テスト例
const account = new BankAccount();
account.deposit(100);
console.log(account.getBalance()); // バランスが期待通りに増加したかをテスト
このように、private
フィールドを直接テストすることはできませんが、それが動作するかどうかをpublic
メソッドを通じて検証することが可能です。
2. `protected`メンバーのテスト
protected
メンバーはサブクラス内からアクセス可能であるため、継承を利用してテストすることができます。テスト用のモッククラスを作成し、protected
メンバーにアクセスすることで、その動作を確認できます。
class Person {
protected age: number;
constructor(age: number) {
this.age = age;
}
}
class PersonTest extends Person {
public getAge(): number {
return this.age; // protectedメンバーにアクセス
}
}
// テスト例
const personTest = new PersonTest(30);
console.log(personTest.getAge()); // protectedメンバーにアクセスしてテスト
このアプローチでは、protected
メンバーをサブクラスでテスト用に公開することで、テストを実施できます。
3. デバッグの際の注意点
デバッグ時には、private
メンバーやprotected
メンバーの値を調べたいことがあります。TypeScriptでは、ブラウザのデバッガやIDEのデバッガを使用することで、クラス内のprivate
フィールドの値を調べることができます。ただし、デバッグ中にフィールドの値を直接変更するのは、設計意図に反する場合があるため、注意が必要です。
デバッグ時のポイントとしては、console.log
を使ってクラス内部の値を確認する方法や、デバッガを使ってフィールドの状態をモニタリングする方法があります。
class User {
private name: string;
constructor(name: string) {
this.name = name;
}
public showName(): void {
console.log(this.name); // デバッグ時に内部状態を表示
}
}
const user = new User("Alice");
user.showName(); // デバッグ用の出力
4. テスト用フレンドクラスの活用
場合によっては、テスト専用のフレンドクラスを作成し、アクセス指定子の制限を緩和してテストする方法もあります。ただし、この方法はテスト目的でのみ使用することが推奨され、実際のコードには導入しない方が良いです。
まとめ
アクセス指定子を使用して堅牢なクラス設計を行う場合、private
やprotected
メンバーのテストやデバッグは間接的な方法を取る必要があります。適切に設計されたpublic
メソッドを介して、メンバーの動作を検証することが推奨されます。また、デバッグ時には、設計意図に反しない範囲で内部状態を確認する方法を使いましょう。
クラスの継承とアクセス指定子の関係
TypeScriptのクラスは、オブジェクト指向プログラミングの基本概念である「継承」をサポートしており、サブクラスが親クラスのメンバーを引き継いで使用できる機能を提供します。アクセス指定子は、この継承時のメンバーのアクセス範囲を制御する重要な役割を果たします。ここでは、アクセス指定子が継承にどのように影響するかを解説します。
1. publicメンバーと継承
public
に指定されたメンバーは、サブクラスやインスタンスから自由にアクセスできます。親クラスのpublic
メンバーは、サブクラスでそのまま継承され、外部からもサブクラス経由でアクセス可能です。
class Animal {
public name: string;
constructor(name: string) {
this.name = name;
}
public speak(): string {
return `${this.name} is making a sound.`;
}
}
class Dog extends Animal {
public speak(): string {
return `${this.name} is barking.`;
}
}
const dog = new Dog("Max");
console.log(dog.speak()); // "Max is barking."
この例では、name
フィールドはpublic
として定義されているため、サブクラスDog
や外部からも直接アクセスできます。
2. privateメンバーと継承
private
に指定されたメンバーは、親クラス内でのみアクセス可能で、サブクラスからも直接アクセスできません。private
フィールドやメソッドはクラスの外部だけでなく、継承先からも隠蔽されます。
class Animal {
private age: number;
constructor(age: number) {
this.age = age;
}
private getAge(): number {
return this.age;
}
}
class Dog extends Animal {
constructor(age: number) {
super(age);
}
public getDogAge(): string {
// return this.age; // エラー: 'age'はprivateであるためアクセス不可
return "Cannot access age directly.";
}
}
const dog = new Dog(5);
console.log(dog.getDogAge()); // "Cannot access age directly."
この例では、age
はprivate
として定義されているため、Dog
クラスからも直接アクセスすることはできません。
3. protectedメンバーと継承
protected
に指定されたメンバーは、親クラス内とサブクラス内でアクセス可能です。ただし、外部からはアクセスできません。protected
は、継承を考慮したアクセス制御に適しており、サブクラスで親クラスのデータやメソッドにアクセスできるようにしつつ、外部からのアクセスを制限します。
class Animal {
protected species: string;
constructor(species: string) {
this.species = species;
}
}
class Dog extends Animal {
constructor(species: string) {
super(species);
}
public getSpecies(): string {
return this.species; // protectedメンバーにアクセス可能
}
}
const dog = new Dog("Canine");
console.log(dog.getSpecies()); // "Canine"
この例では、species
はprotected
として定義されており、Dog
クラスからはアクセスできますが、クラスの外部からはアクセスできません。
4. アクセス指定子とメソッドのオーバーライド
継承時にメソッドをオーバーライドする場合、アクセス指定子の変更には注意が必要です。オーバーライドしたメソッドに対して、元のメソッドよりも制約の厳しいアクセス指定子を付けることはできません。たとえば、親クラスのpublic
メソッドをサブクラスでprivate
にすることはできませんが、逆にprivate
メソッドをpublic
として公開することは可能です。
class Animal {
public makeSound(): string {
return "Some generic animal sound";
}
}
class Dog extends Animal {
// オーバーライドしてよりアクセス範囲を広げるのは許容される
public makeSound(): string {
return "Bark!";
}
}
const dog = new Dog();
console.log(dog.makeSound()); // "Bark!"
アクセス指定子の理解を深めてクラス設計に活用することで、より安全で拡張性の高いコードを実現できます。特に継承を行う際には、public
、private
、protected
の違いを意識し、設計を行うことが重要です。
TypeScriptにおけるアクセス指定子の制限と回避策
TypeScriptのアクセス指定子は、クラス設計を安全かつ堅牢にするために非常に役立ちますが、いくつかの制限もあります。これらの制限を理解し、それに対応するための回避策を用いることで、柔軟かつ効率的なクラス設計が可能になります。
1. `private`メンバーへの外部アクセスが必要な場合
TypeScriptのprivate
メンバーはクラス外部から直接アクセスすることができません。時には、テストやデバッグ、特定の場面でprivate
メンバーにアクセスしたい場合があるかもしれません。この制限を回避するために、いくつかの方法があります。
回避策1: `getter`と`setter`を使用する
private
メンバーに対してアクセスや操作が必要な場合、getter
とsetter
を使って適切な方法でアクセスできるようにすることが一般的な回避策です。これにより、private
メンバーの外部からのアクセスを制御しつつ、必要な場面ではデータにアクセスできるようにします。
class User {
private _password: string = "initialPassword";
public get password(): string {
// パスワードの安全な読み出し処理
return "*****";
}
public set password(newPassword: string) {
// パスワードの安全な設定処理
if (newPassword.length >= 8) {
this._password = newPassword;
} else {
console.log("パスワードは8文字以上でなければなりません。");
}
}
}
回避策2: TypeScriptの型キャストを使用する
テストやデバッグなどでprivate
メンバーに直接アクセスしたい場合、TypeScriptの型キャストを利用して強制的にアクセスすることが可能です。ただし、これはあくまで特殊なケースでの使用に限定され、通常の開発では推奨されません。
class Person {
private age: number = 30;
}
const person = new Person();
console.log((person as any).age); // 型キャストで強制的にアクセス(非推奨)
2. `protected`メンバーの限界
protected
メンバーはサブクラス内でしかアクセスできず、サブクラスのインスタンスからはアクセスできません。この制限により、サブクラスがインスタンス化された後にフィールドを直接操作したい場合に制約が生じることがあります。
回避策: メソッドを利用して公開する
protected
メンバーをサブクラスのインスタンスから操作したい場合、public
メソッドを用いて必要な操作を行う仕組みを提供することで、制限を回避することができます。
class Animal {
protected species: string;
constructor(species: string) {
this.species = species;
}
}
class Dog extends Animal {
constructor(species: string) {
super(species);
}
public getSpecies(): string {
return this.species; // protectedメンバーを間接的に公開
}
}
const dog = new Dog("Canine");
console.log(dog.getSpecies()); // "Canine"
3. クラス外部の関数からのアクセス制限
アクセス指定子はクラス外部からのメンバーの直接操作を制限しますが、クラス外部の関数から特定のフィールドやメソッドにアクセスしたい場合もあるかもしれません。この制限を回避するための方法もあります。
回避策: 関数をクラスメソッドとして定義する
クラスのメンバーに外部からアクセスさせたい場合、その機能をクラスの一部として提供することで、制御された方法でアクセス可能にすることができます。
class Calculator {
private result: number = 0;
public add(value: number): void {
this.result += value;
}
public getResult(): number {
return this.result;
}
}
const calculator = new Calculator();
calculator.add(10);
console.log(calculator.getResult()); // 計算結果を取得
4. `readonly`とアクセス指定子の組み合わせ
TypeScriptでは、フィールドにreadonly
修飾子を付けることで、初期化後の変更を防ぐことができます。しかし、readonly
とアクセス指定子を組み合わせることで、フィールドの可視性や操作の制御に関してさらに厳密な制約を追加できます。
class Car {
private readonly model: string;
constructor(model: string) {
this.model = model;
}
public getModel(): string {
return this.model;
}
}
const car = new Car("Tesla");
console.log(car.getModel()); // "Tesla"
// car.model = "Ford"; // エラー: modelはreadonlyであり変更不可
まとめ
TypeScriptにおけるアクセス指定子は、クラスの安全性や堅牢性を高めるために有効ですが、場合によっては制約もあります。これらの制限を理解し、getter
やsetter
、メソッドの公開、型キャストなどを適切に活用することで、柔軟な設計が可能になります。
演習問題:アクセス指定子を使った設計の実践
ここでは、アクセス指定子を使ったクラス設計を実践するための演習問題を用意しました。これに取り組むことで、TypeScriptのアクセス指定子に関する理解を深め、実際のコードに活用するスキルを向上させることができます。
演習1: アクセス指定子を使ってクラスの設計を強化する
以下の要件を満たすLibraryBook
クラスを設計してください:
- 書籍のタイトル、著者、ISBN番号を保持するフィールドを作成します。
title
とauthor
は外部からアクセスできるが、ISBN番号はクラス内でのみ扱えるようにする(アクセス指定子を使用)。- 書籍の貸し出し状態を保持するフィールド
isBorrowed
を作成し、これをprivate
に設定します。 - 書籍の貸し出し状態を確認するための
isAvailable()
メソッドを作成し、貸出可能な場合はtrue
、そうでない場合はfalse
を返します。 - 書籍を貸し出すための
borrowBook()
メソッドと、書籍を返却するためのreturnBook()
メソッドを作成します。これらはisBorrowed
フィールドを適切に変更します。
コード例
class LibraryBook {
public title: string;
public author: string;
private isbn: string;
private isBorrowed: boolean;
constructor(title: string, author: string, isbn: string) {
this.title = title;
this.author = author;
this.isbn = isbn;
this.isBorrowed = false;
}
public isAvailable(): boolean {
return !this.isBorrowed;
}
public borrowBook(): void {
if (this.isAvailable()) {
this.isBorrowed = true;
console.log(`${this.title} has been borrowed.`);
} else {
console.log(`${this.title} is already borrowed.`);
}
}
public returnBook(): void {
if (!this.isAvailable()) {
this.isBorrowed = false;
console.log(`${this.title} has been returned.`);
} else {
console.log(`${this.title} was not borrowed.`);
}
}
// ISBNを取得するためのメソッド
public getISBN(): string {
return this.isbn;
}
}
// インスタンスを作成し、動作を確認
const book = new LibraryBook("TypeScript Guide", "John Doe", "123-4567890123");
console.log(book.title); // "TypeScript Guide"
console.log(book.isAvailable()); // true
book.borrowBook(); // "TypeScript Guide has been borrowed."
console.log(book.isAvailable()); // false
book.returnBook(); // "TypeScript Guide has been returned."
演習2: protected
を使ったクラスの拡張
次に、LibraryBook
クラスを拡張して、EBook
クラスを作成してください。
EBook
クラスはLibraryBook
を継承し、ページ数を管理するpages
フィールドを追加します。このフィールドは、サブクラス内でのみアクセスできるようにprotected
で定義します。- 電子書籍を読むための
readBook()
メソッドを追加し、書籍が貸し出されていない場合のみページを読み進めることができるようにします。
class EBook extends LibraryBook {
protected pages: number;
private currentPage: number;
constructor(title: string, author: string, isbn: string, pages: number) {
super(title, author, isbn);
this.pages = pages;
this.currentPage = 0;
}
public readBook(): void {
if (this.isAvailable()) {
console.log("You need to borrow this book before reading.");
} else if (this.currentPage < this.pages) {
this.currentPage++;
console.log(`Reading page ${this.currentPage} of ${this.pages}.`);
} else {
console.log("You have finished the book.");
}
}
}
// EBookのインスタンスを作成し、動作を確認
const ebook = new EBook("Advanced TypeScript", "Jane Smith", "987-6543210987", 300);
ebook.borrowBook(); // "Advanced TypeScript has been borrowed."
ebook.readBook(); // "Reading page 1 of 300."
ebook.readBook(); // "Reading page 2 of 300."
演習問題の目的
これらの演習は、アクセス指定子を適切に使ってクラスのデータを保護し、適切な操作のみを許可する設計の実践を目的としています。また、クラスの拡張や継承におけるアクセス指定子の使い方も学ぶことができます。実際にコードを書いて動作を確認しながら、アクセス指定子の役割や効果を理解しましょう。
まとめ
本記事では、TypeScriptにおけるアクセス指定子を使ってクラス設計を強化する方法について解説しました。public
、private
、protected
の3つのアクセス指定子を理解し、適切に使い分けることで、クラスの堅牢性を高め、外部からの不要なアクセスや誤操作を防ぐことができます。また、getter
やsetter
を活用することで、フィールドへのアクセスや操作をより柔軟に管理できることも学びました。
演習問題を通じて、アクセス指定子を使ったクラス設計の実践力を高め、TypeScriptでのオブジェクト指向プログラミングの理解が深まったかと思います。今後の開発に役立ててください。
コメント