TypeScriptでアクセス指定子を使ってクラス設計を強化する方法

TypeScriptでオブジェクト指向プログラミングを行う際、クラスのフィールドやメソッドへのアクセス制御は非常に重要です。この制御を適切に行うことで、クラスの設計を堅牢にし、意図しない操作や不正なアクセスを防ぐことができます。TypeScriptは、クラスのフィールドやメソッドに対してアクセス指定子(publicprivateprotected)を使用することで、アクセス範囲を明確に定義できる特徴を持っています。

本記事では、TypeScriptにおけるアクセス指定子を用いたクラス設計の方法について解説し、設計の堅牢性を高めるためのベストプラクティスを紹介します。

目次

TypeScriptにおけるアクセス指定子の概要


TypeScriptでは、クラスのメンバー(フィールドやメソッド)に対してアクセス制御を行うために、アクセス指定子を使用します。これにより、クラス外からアクセス可能な範囲を制限することができ、クラス設計のセキュリティや堅牢性が向上します。

TypeScriptで使用できる主なアクセス指定子は以下の3つです:

public


public指定子は、クラスのメンバーを外部から自由にアクセスできることを意味します。アクセス指定子を指定しない場合、デフォルトでpublicが適用されます。

private


private指定子は、そのクラス内でのみメンバーへのアクセスを許可します。他のクラスやインスタンスからは直接アクセスできません。

protected


protected指定子は、クラス自身およびそのサブクラスからのみアクセス可能です。サブクラスでの継承を前提としたアクセス制御を行いたい場合に有効です。

これらの指定子を使うことで、クラスの設計をより堅牢に保つことが可能です。

public、private、protectedの違い


TypeScriptでクラスを設計する際、publicprivateprotectedというアクセス指定子を使ってフィールドやメソッドのアクセス制御を行います。これらはクラスのメンバーに対するアクセス可能範囲を決定する重要な要素です。それぞれの違いについて詳しく見ていきましょう。

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. データのカプセル化


privateprotectedのアクセス指定子を使用することで、クラス内のデータを外部から直接操作できないように制御できます。これにより、クラスの内部状態を隠蔽し、予期しない変更を防ぐことができるため、クラスが意図通りに動作することが保証されます。

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文字以上でなければなりません。");
    }
  }
}

上記の例では、usernamepasswordフィールドがprivate指定されているため、外部から直接アクセスできません。しかし、getUsernamesetPasswordといったメソッドを介して、必要な操作を行うことができます。これにより、パスワードが直接変更されるリスクを回避し、クラス内部のデータが不正に操作されるのを防ぎます。

クラス設計の堅牢性向上


アクセス指定子を使ってクラスのフィールドを保護することで、意図しないデータ変更を防ぎ、クラスの堅牢性が大幅に向上します。たとえば、フィールドをprivateにすることで、クラスの利用者がデータに直接アクセスして変更するのを防ぎ、プログラムの安定性が保たれます。

また、クラスの内部実装を隠蔽することで、後に内部構造を変更する際に、外部からの影響を最小限に抑えることができます。これにより、クラスを安全に拡張・修正でき、保守性が向上します。

カプセル化とアクセス指定子の使い分け


アクセス指定子を使い分けることで、クラス設計の意図を明確にし、他の開発者がクラスを使用する際にどの部分が安全に操作できるかを理解しやすくなります。例えば、フィールドはprivateにして外部から直接アクセスできないようにし、必要な情報や操作はpublicメソッドを通じて提供する、という形がよく採用されます。

カプセル化を適切に行うことで、クラスの内部状態を保護し、安定したコードを維持することができます。

実際のコード例:アクセス指定子を使ったクラス設計


アクセス指定子を効果的に使うことで、クラス設計を堅牢にし、外部からの不要なアクセスや誤操作を防ぐことができます。ここでは、アクセス指定子を活用したクラス設計の具体的なコード例を見ていきます。

例:銀行口座クラス


以下は、publicprivateprotectedを使用したシンプルな銀行口座クラスの例です。このクラスでは、残高や口座番号といった重要な情報は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

コードの解説

  • accountNumberbalanceフィールドはprivate指定されており、クラス外からは直接アクセスできません。これにより、外部からの誤ったデータ操作や不正アクセスを防ぎます。
  • 口座番号を取得するgetAccountNumberメソッドと、残高を取得するgetBalanceメソッドはpublicとして定義されているため、外部から呼び出してデータを取得できます。
  • depositwithdrawメソッドを使用することで、残高の操作を行います。これらのメソッドを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にアクセスできるようになっていますが、外部からは直接アクセスできません。
  • サブクラスEmployeeprivateフィールドemployeeIdを持ち、これも外部から直接アクセスできないようにしています。
  • getEmployeeInfoメソッドは、nameemployeeIdの情報をまとめて外部に提供します。

このように、アクセス指定子を効果的に使い分けることで、クラスのデータを安全に保護しつつ、必要な情報だけを外部に公開できます。

応用例:getterとsetterの活用


TypeScriptにおいて、アクセス指定子とgetterおよびsetterを組み合わせることで、クラスのフィールドへのアクセスをさらに制御し、柔軟で安全なデータ管理が可能になります。gettersetterは、フィールドへの読み書きに特別な処理を追加できるメソッドであり、直接フィールドにアクセスすることなく、その操作を管理できます。

getterとsetterの基本


getterは、フィールドの値を取得する際に特別な処理を行うためのメソッドです。setterは、フィールドに値を設定する際に追加のバリデーションやロジックを実行できます。これにより、直接フィールドにアクセスする代わりに、安全な方法でデータを操作できます。

以下は、gettersetterを使った実例です。

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. データへの安全なアクセス


gettersetterを使うことで、フィールドへの不正な操作を防ぎつつ、必要な処理を追加できます。たとえば、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. 内部データの抽象化


gettersetterを使うことで、フィールドの内部実装を隠し、外部からはその実装に依存しない形で操作できます。これにより、フィールドの内部実装が変更されたとしても、外部からのアクセス方法を変える必要がなくなります。例えば、計算された結果を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

このように、gettersetterを使うことで、フィールドの操作をより柔軟に管理し、安全で堅牢なクラス設計を実現することができます。

アクセス指定子を使用したテストとデバッグの注意点


アクセス指定子を用いてクラス設計を行う際、テストやデバッグの過程でいくつかの注意点が生じます。特に、privateprotectedのメンバーは外部から直接アクセスできないため、テストやデバッグを行う際には特別な工夫が必要になります。ここでは、アクセス指定子を使用したコードにおけるテストとデバッグの際に考慮すべき点について解説します。

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. テスト用フレンドクラスの活用


場合によっては、テスト専用のフレンドクラスを作成し、アクセス指定子の制限を緩和してテストする方法もあります。ただし、この方法はテスト目的でのみ使用することが推奨され、実際のコードには導入しない方が良いです。

まとめ


アクセス指定子を使用して堅牢なクラス設計を行う場合、privateprotectedメンバーのテストやデバッグは間接的な方法を取る必要があります。適切に設計された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."

この例では、ageprivateとして定義されているため、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"

この例では、speciesprotectedとして定義されており、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!"

アクセス指定子の理解を深めてクラス設計に活用することで、より安全で拡張性の高いコードを実現できます。特に継承を行う際には、publicprivateprotectedの違いを意識し、設計を行うことが重要です。

TypeScriptにおけるアクセス指定子の制限と回避策


TypeScriptのアクセス指定子は、クラス設計を安全かつ堅牢にするために非常に役立ちますが、いくつかの制限もあります。これらの制限を理解し、それに対応するための回避策を用いることで、柔軟かつ効率的なクラス設計が可能になります。

1. `private`メンバーへの外部アクセスが必要な場合


TypeScriptのprivateメンバーはクラス外部から直接アクセスすることができません。時には、テストやデバッグ、特定の場面でprivateメンバーにアクセスしたい場合があるかもしれません。この制限を回避するために、いくつかの方法があります。

回避策1: `getter`と`setter`を使用する


privateメンバーに対してアクセスや操作が必要な場合、gettersetterを使って適切な方法でアクセスできるようにすることが一般的な回避策です。これにより、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におけるアクセス指定子は、クラスの安全性や堅牢性を高めるために有効ですが、場合によっては制約もあります。これらの制限を理解し、gettersetter、メソッドの公開、型キャストなどを適切に活用することで、柔軟な設計が可能になります。

演習問題:アクセス指定子を使った設計の実践


ここでは、アクセス指定子を使ったクラス設計を実践するための演習問題を用意しました。これに取り組むことで、TypeScriptのアクセス指定子に関する理解を深め、実際のコードに活用するスキルを向上させることができます。

演習1: アクセス指定子を使ってクラスの設計を強化する

以下の要件を満たすLibraryBookクラスを設計してください:

  • 書籍のタイトル、著者、ISBN番号を保持するフィールドを作成します。
  • titleauthorは外部からアクセスできるが、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におけるアクセス指定子を使ってクラス設計を強化する方法について解説しました。publicprivateprotectedの3つのアクセス指定子を理解し、適切に使い分けることで、クラスの堅牢性を高め、外部からの不要なアクセスや誤操作を防ぐことができます。また、gettersetterを活用することで、フィールドへのアクセスや操作をより柔軟に管理できることも学びました。

演習問題を通じて、アクセス指定子を使ったクラス設計の実践力を高め、TypeScriptでのオブジェクト指向プログラミングの理解が深まったかと思います。今後の開発に役立ててください。

コメント

コメントする

目次