TypeScriptのアクセス修飾子を完全理解!実例10選で学ぶその使い方

TypeScriptのアクセス修飾子の実例と解説のイメージTypeScript
この記事は約25分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

TypeScriptは、JavaScriptのスーパーセットとして知られるプログラミング言語であり、大規模なアプリケーション開発やライブラリ設計において非常に役立ちます。

TypeScriptを学ぶ際の最初のステップとして、「アクセス修飾子」の概念は欠かせません。

これはオブジェクト指向プログラミングにおける重要な概念の一つで、クラスのプロパティやメソッドの可視性を制御するためのものです。

この記事では、TypeScriptでのアクセス修飾子の役割や使い方を、具体的な実例を交えながら詳細に解説します。

具体的には、基本的な使い方から応用例、さらには注意点やカスタマイズ方法についても触れていきます。

初心者の方はもちろん、中級者や上級者の方にも役立つ情報を提供することを目指しています。

さらに、本記事を通して、実際の開発現場でのベストプラクティスや、より良いコード設計のためのヒントも得られるでしょう。

それでは、まずはTypeScriptのアクセス修飾子の基本的な概念から学んでいきましょう。

●TypeScriptのアクセス修飾子とは

TypeScriptは、JavaScriptに静的型付けを追加したプログラミング言語として広く利用されています。

TypeScriptが持つ特徴の一つとして、アクセス修飾子という概念があります。

アクセス修飾子は、クラスの中のメンバー(変数やメソッド)の可視性やアクセス範囲を制御するためのキーワードです。
これにより、オブジェクト指向プログラミングの原則に従い、データのカプセル化や情報隠蔽を実現することができます。

○基本の修飾子:public, private, protected, readonly

  • public:これはデフォルトの修飾子で、どこからでもアクセス可能なメンバーを示します。
  • private:この修飾子がついたメンバーは、そのクラスの内部からのみアクセス可能です。
  • protected:この修飾子は、そのクラスとサブクラスの内部からのみアクセスが可能なメンバーを表します。
  • readonly:この修飾子を持つプロパティは、一度値が設定されるとその後変更できなくなります。

このコードでは基本的なアクセス修飾子を使ってクラスを定義しています。

この例ではPersonクラスを定義し、各アクセス修飾子を利用してプロパティを設定しています。

class Person {
    public name: string; // どこからでもアクセス可能
    private age: number; // Personクラスの内部からのみアクセス可能
    protected address: string; // Personクラスとサブクラスからのみアクセス可能
    readonly birthYear: number; // 一度設定されると変更不可

    constructor(name: string, age: number, address: string, birthYear: number) {
        this.name = name;
        this.age = age;
        this.address = address;
        this.birthYear = birthYear;
    }
}

上記のコードを実行すると、Personクラスのインスタンスを作成し、そのメンバーにアクセスすることができます。

しかし、privateやprotectedなどの修飾子がついているメンバーには外部から直接アクセスすることはできません。

アクセス修飾子を理解することで、データを適切に隠蔽し、コードの安全性を高めることができます。

特に大規模なプロジェクトやライブラリの設計時には、このような機能が非常に役立ちます。

●アクセス修飾子の詳しい使い方

アクセス修飾子は、クラス内のプロパティやメソッドの可視性を制御するためのキーワードです。

これにより、意図しない場所からのアクセスを制限したり、データの整合性を保ったりすることができます。

TypeScriptでは、JavaScriptにはない4つのアクセス修飾子を提供しています。

それぞれのアクセス修飾子の特徴と使い方について、具体的なサンプルコードと共に詳しく見ていきましょう。

○サンプルコード1:publicの基本的な使い方

このコードでは、最も基本的なアクセス修飾子であるpublicを使ってプロパティを定義する方法を表しています。

この例では、Personクラスのnameプロパティを外部からアクセス可能にしています。

class Person {
    public name: string;

    constructor(name: string) {
        this.name = name;
    }
}

const taro = new Person('Taro');
console.log(taro.name); // Taro

上記のコードを実行すると、Taroという文字が出力されます。

publicを指定したプロパティやメソッドは、クラスの外部からも自由にアクセスできるため、taro.nameのように直接参照することができました。

○サンプルコード2:privateとは?

このコードでは、privateアクセス修飾子を使って、クラスの外部からのアクセスを制限する方法を表しています。

この例では、Personクラスのageプロパティを外部からのアクセスを禁止しています。

class Person {
    private age: number;

    constructor(age: number) {
        this.age = age;
    }

    showAge() {
        console.log(`My age is ${this.age}.`);
    }
}

const hanako = new Person(25);
hanako.showAge(); // My age is 25.

ageプロパティにprivateを指定することで、クラスの外部からはageに直接アクセスすることができなくなります。

しかし、クラス内部のメソッドからはアクセス可能なので、showAgeメソッドを通して間接的にageの値を参照することができました。

○サンプルコード3:protectedの役割

このコードでは、protectedアクセス修飾子を使って、サブクラスからのみアクセスを許可する方法を表しています。

この例では、Animalクラスのlegsプロパティをサブクラスからアクセス可能にしています。

class Animal {
    protected legs: number;

    constructor(legs: number) {
        this.legs = legs;
    }
}

class Dog extends Animal {
    bark() {
        console.log(`I have ${this.legs} legs and I can bark!`);
    }
}

const shiro = new Dog(4);
shiro.bark(); // I have 4 legs and I can bark!

legsプロパティにprotectedを指定することで、Dogクラスのようなサブクラスからはアクセスできるようになりますが、クラスの外部からはアクセスできなくなります。

このため、shiro.legsのように直接参照しようとするとエラーが発生しますが、barkメソッドを通じて間接的に参照することはできました。

○サンプルコード4:readonlyを使ったプロパティ

TypeScriptにはreadonlyというアクセス修飾子が存在します。

この修飾子は、一度設定されたプロパティの値を変更できないようにするためのものです。

特に、オブジェクト指向のプログラミングで不変性を重視したい場合や、一度セットされたプロパティの値が変わっては困る場合に役立ちます。

readonlyを使ったサンプルコードを紹介します。

class Person {
    readonly name: string;
    readonly birthYear: number;

    constructor(name: string, birthYear: number) {
        this.name = name;
        this.birthYear = birthYear;
    }

    showAge(currentYear: number): number {
        return currentYear - this.birthYear;
    }
}

let tanaka = new Person("田中", 1990);
console.log(tanaka.name); // 田中
console.log(tanaka.showAge(2023)); // 33
// tanaka.name = "佐藤"; // エラー! readonlyプロパティは再代入不可

このコードでは、PersonというクラスにnamebirthYearというreadonlyプロパティを持っています。

コンストラクタでこれらの値をセットすると、それ以降は再代入ができなくなります。

そのため、最後の行でtanaka.nameに対して値を再代入しようとするとエラーが発生します。

このような特性を持つreadonlyは、プロパティの値が変わらないことを保証するための強力なツールとなります。

さて、上記のサンプルコードを実行すると、”田中”という名前が出力され、次に33という年齢が出力されます。

しかし、最後の行でreadonlyプロパティの再代入を試みると、コンパイル時にエラーが発生します。

続いて、readonlyと似た機能であるconstとの違いについて触れていきましょう。

constは変数の再代入を防ぐためのものであり、基本的なデータ型やオブジェクトの全体を不変にします。

一方、readonlyはクラスのプロパティに特化しており、そのプロパティの値だけを変更不可能にすることができます。

●応用例:アクセス修飾子を駆使してより安全なコードを書こう

TypeScriptを書く際にアクセス修飾子を適切に使用することは、安全で保守性の高いコードを書くための基本です。

特に大規模なアプリケーションやライブラリを開発する際には、アクセス修飾子の正確な理解とその活用方法は必須となります。

ここでは、アクセス修飾子を応用したさまざまな実例を通じて、その魅力や強力さを深く探っていきます。

○サンプルコード5:privateとgetter/setterの組み合わせ

このコードではprivate修飾子とgetter/setterを組み合わせて、外部から直接アクセスできないプロパティを持つクラス表しています。

この例では、Userクラスのpasswordフィールドを外部から直接変更できないようにしています。

class User {
    private _password: string;

    constructor(password: string) {
        this._password = password;
    }

    // パスワードを取得するgetter
    get password(): string {
        return "****"; // 実際のパスワードは返さず、伏字にして返す
    }

    // パスワードを設定するsetter
    set password(newPassword: string) {
        if (newPassword.length >= 8) {
            this._password = newPassword;
        } else {
            console.log("パスワードは8文字以上で設定してください。");
        }
    }
}

const user = new User("myOldPassword");
console.log(user.password);  // **** と表示される
user.password = "newPass";   // パスワードは8文字以上で設定してください。

上記の例でUserクラスのインスタンスを生成した際に、passwordフィールドの値を直接外部から取得することはできません。

取得しようとすると伏字になります。

また、setterを使用してパスワードを設定する際には、8文字以上でなければ変更ができないようにしています。

○サンプルコード6:protectedを利用した継承

このコードではprotected修飾子を使った継承の例を表しています。

この例では、BaseClassbaseValueフィールドを継承先のDerivedClassから参照しています。

class BaseClass {
    protected baseValue: number;

    constructor(value: number) {
        this.baseValue = value;
    }
}

class DerivedClass extends BaseClass {
    showValue() {
        console.log(this.baseValue);
    }
}

const obj = new DerivedClass(123);
obj.showValue();  // 123 と表示される

上記のコードを実行すると、DerivedClassBaseClassを継承しているため、baseValueフィールドの値をshowValueメソッドで表示できます。

○サンプルコード7:readonlyとconstの違い

このコードでは、readonly修飾子とconstキーワードの違いを表しています。

この例では、readonlyはクラスのプロパティに、constは変数に適用されています。

class MyClass {
    readonly classValue: number = 10;
}

const myConstValue: number = 20;

const obj = new MyClass();
console.log(obj.classValue);  // 10 と表示される
console.log(myConstValue);    // 20 と表示される

上記のコードを実行すると、readonlyで宣言されたclassValueプロパティと、constで宣言されたmyConstValue変数の値を取得して表示します。

○サンプルコード8:アクセス修飾子を使ったライブラリの設計

ライブラリやモジュールの設計時、アクセス修飾子は非常に重要な役割を果たします。

ライブラリの内部の実装を隠蔽しつつ、必要な部分のみを外部に公開することで、使用者にとって安全でわかりやすいAPIを提供できるようになります。

ここでは、アクセス修飾子を活用してTypeScriptでライブラリを設計する方法をサンプルコードとともに解説します。

このコードでは、アクセス修飾子を活用して、ライブラリ内部の詳細を隠蔽し、必要なインターフェースだけを外部に公開する方法を表しています。

この例では、Database クラスを作成し、外部に公開するメソッドと内部だけで使用するメソッドを明確に分けています。

// ライブラリのサンプルコード
class Database {
    // 外部に公開するプロパティ
    public isConnected: boolean = false;

    // 内部でのみ使用するプロパティ
    private connection: any;

    // 外部からの接続メソッド
    public connect() {
        // ここでの接続処理は省略
        this.connection = this.initConnection();
        this.isConnected = true;
    }

    // 内部でのみ使用する接続初期化メソッド
    private initConnection() {
        // 実際の接続処理は省略
        return {};
    }

    // 外部に公開するデータ取得メソッド
    public fetchData(query: string) {
        // データ取得処理は省略
        return [];
    }
}

上記のコードを利用する際、使用者はisConnectedconnect()fetchData(query: string)のみを利用できます。

内部で使用されるconnectioninitConnection()は、外部からアクセスすることができません。

これにより、ライブラリの安全性が高まり、意図しない操作やアクセスを防ぐことができます。

外部からこのライブラリを使用する場合、次のようになります。

const db = new Database();
db.connect();
const data = db.fetchData('SELECT * FROM users');

上記のコードでデータベースに接続し、データを取得することができます。

Databaseクラスの内部実装を知らなくても、シンプルに操作することができます。

アクセス修飾子を利用することで、安全で使いやすいライブラリを設計することが可能となります。

○サンプルコード9:外部モジュールとの連携

TypeScriptを使用する場合、特に大規模なプロジェクトや他者が作成したライブラリを使用する際に、外部モジュールとの連携が不可欠です。

ここでは、アクセス修飾子を使用して外部モジュールと連携する方法について解説します。

このコードでは、外部モジュールから提供されるクラスを拡張して、アクセス修飾子を用いた新しいメソッドを追加することを表しています。

この例では、外部モジュールのExternalLibraryClassを継承し、新たなメソッドをprivate修飾子で追加しています。

// 外部モジュールのイメージ
// externalModule.ts
export class ExternalLibraryClass {
    protected methodFromLibrary() {
        console.log("外部モジュールのメソッド");
    }
}

// メインのコード
import { ExternalLibraryClass } from './externalModule';

class ExtendedClass extends ExternalLibraryClass {
    // 外部モジュールのメソッドを拡張
    public extendedMethod() {
        console.log("拡張メソッドを実行");
        this.privateMethod();
        this.methodFromLibrary();
    }

    // 新しいprivateメソッドの追加
    private privateMethod() {
        console.log("プライベートなメソッド");
    }
}

const instance = new ExtendedClass();
instance.extendedMethod();

上記のコードを実行すると、ExtendedClassのインスタンスが生成され、そのextendedMethodメソッドが呼び出されます。

その結果、次のログが出力されます。

拡張メソッドを実行
プライベートなメソッド
外部モジュールのメソッド

このように、TypeScriptのアクセス修飾子を用いて、外部モジュールのクラスを安全に拡張し、新しい機能を追加することができます。

しかし、この方法には注意点も存在します。

外部モジュールのクラス構造やメソッドの挙動が変更された場合、それに伴い継承先のクラスも更新する必要があります。

そのため、外部モジュールのバージョンアップや変更履歴を常にチェックすることが重要です。

また、独自のカスタマイズが必要な場合は、アクセス修飾子を使用して自身のクラスやメソッドを保護することで、意図しない変更やアクセスからコードを守ることができます。

例えば、次のようにExternalLibraryClassをカスタマイズして新しいメソッドを追加する場合のコードを見てみましょう。

import { ExternalLibraryClass } from './externalModule';

class CustomizedClass extends ExternalLibraryClass {
    public customMethod() {
        if (this.checkCondition()) {
            console.log("カスタマイズした処理を実行");
            this.methodFromLibrary();
        } else {
            console.log("条件に合致しないため実行しない");
        }
    }

    private checkCondition(): boolean {
        // ここで特定の条件をチェックする
        return true; // 今回は常にtrueを返す
    }
}

const instance2 = new CustomizedClass();
instance2.customMethod();

このコードを実行すると、次のようなログが表示されることが期待されます。

カスタマイズした処理を実行
外部モジュールのメソッド

○サンプルコード10:アクセス修飾子のベストプラクティス

アクセス修飾子の使い方を理解すると、TypeScriptを使ってより安全で読みやすいコードを書くための力強いツールを手に入れることができます。

しかし、最も効果的にこれらを利用するためのベストプラクティスは何でしょうか?

ここでは、アクセス修飾子を効果的に使用するための一般的な指針とサンプルコードを紹介します。

□必要最小限の可視性を使用する

このコードでは、最小限のアクセス権を持つ修飾子を選択することで、クラスの外部からの不要なアクセスを制限しています。

この例では、private修飾子を使用して、MyClassクラスの外部から直接アクセスできないプロパティを作成しています。

class MyClass {
    private secretData: string;

    constructor(data: string) {
        this.secretData = data;
    }

    // 外部からアクセスするための公開メソッド
    revealSecret(): string {
        return this.secretData;
    }
}

const instance = new MyClass("秘密のデータ");
console.log(instance.revealSecret());  // "秘密のデータ"を出力

上記のコードを実行すると、”秘密のデータ”という文字列が出力されます。

しかし、secretDataプロパティ自体は外部からアクセスできないため、安全に情報を隠蔽できます。

□不変性を維持するためにreadonlyを活用する

このコードでは、初期化後に変更を許容しないプロパティを持つクラスを表しています。

この例では、readonly修飾子を使ってimmutableDataプロパティが後から変更できないようにしています。

class ImmutableClass {
    readonly immutableData: string;

    constructor(data: string) {
        this.immutableData = data;
    }
}

const immutableInstance = new ImmutableClass("変更不可のデータ");
// immutableInstance.immutableData = "新しいデータ";  // エラー!変更できません
console.log(immutableInstance.immutableData);  // "変更不可のデータ"を出力

上記のコードを実行すると、”変更不可のデータ”という文字列が出力されます。

また、immutableDataプロパティへの再代入を試みると、TypeScriptのコンパイルエラーが発生します。

□継承を考慮してprotectedを活用する

このコードでは、サブクラスでのみアクセス可能なプロパティを持つクラスを表しています。

この例では、BaseClassクラス内のprotectedValueプロパティは、派生クラスのDerivedClassからアクセス可能です。

class BaseClass {
    protected protectedValue: number = 10;
}

class DerivedClass extends BaseClass {
    showValue(): void {
        console.log(this.protectedValue);  // 10を出力
    }
}

const derivedInstance = new DerivedClass();
derivedInstance.showValue();

上記のコードを実行すると、数字の10が出力されます。

しかし、BaseClassのインスタンスからはprotectedValueに直接アクセスすることはできません。

●注意点と対処法

TypeScriptを使ってコーディングする際、アクセス修飾子を正しく利用することは非常に重要です。

しかし、その使い方にはいくつかの注意点が存在します。

ここでは、よくある誤解や間違いを解説し、その対処法を実例と共に紹介していきます。

○privateとprotectedの間違い

アクセス修飾子には「private」と「protected」の2つがありますが、これらの違いを正しく理解せずに利用すると意図しない動作やエラーが発生する可能性があります。

このコードでは「private」と「protected」の動作の違いを表しています。

この例では、親クラスで「private」および「protected」修飾子を持つプロパティを持ち、サブクラスでそのプロパティへのアクセスを試みることで違いを実感します。

class ParentClass {
    private privateProperty: string = "private";
    protected protectedProperty: string = "protected";
}

class ChildClass extends ParentClass {
    displayProperties() {
        // console.log(this.privateProperty); // エラー: 'privateProperty'はクラス'ParentClass'のみでアクセス可能です。
        console.log(this.protectedProperty); // 出力: protected
    }
}

const child = new ChildClass();
child.displayProperties();

この例で見るように、サブクラスから親クラスのprivate修飾子を持つプロパティにアクセスしようとするとエラーになりますが、protected修飾子を持つプロパティはアクセス可能です。

○アクセス修飾子の使いすぎ

アクセス修飾子は適切に使われる場合、コードの安全性や保守性を高めるための有効な手段となります。

しかし、過度に使用するとコードが複雑になり、逆に読みにくくなることがあります。

このコードでは、過度にアクセス修飾子を使用したクラスの例を表しています。

この例では、全てのメソッドやプロパティにアクセス修飾子をつけ、その結果どのようになるかを確認します。

class OverusedModifiers {
    private prop1: string;
    protected prop2: number;
    public prop3: boolean;

    private method1() {}
    protected method2() {}
    public method3() {}
}

const instance = new OverusedModifiers();
// instance.prop1やinstance.method1()など、アクセス修飾子により外部からのアクセスが制限される。

上の例のように、全てのプロパティやメソッドに修飾子をつけると、クラスの外部からどのプロパティやメソッドにアクセスできるのかが一目瞭然になりますが、逆に冗長になりがちです。

適切なバランスでの使用が求められます。

●カスタマイズ方法:TypeScriptの設定でアクセス修飾子の挙動をカスタマイズ

TypeScriptは柔軟な言語であり、設定ファイルtsconfig.jsonを通じて、アクセス修飾子やその他の機能の動作をカスタマイズできます。

特に、アクセス修飾子の挙動に関連した設定は、プロジェクトのニーズやチームのコーディング規約に合わせて調整することが可能です。

○tsconfig.jsonの設定例

TypeScriptの設定ファイルであるtsconfig.jsonの一部を抜粋したものを紹介します。

この設定例では、アクセス修飾子に関連したいくつかの重要な設定項目を取り上げて説明します。

{
  "compilerOptions": {
    "strictPropertyInitialization": true,
    "noUnusedParameters": true,
    "noUnusedLocals": true
  }
}

このコードでは次の3つのオプションを使ってTypeScriptの動作をカスタマイズしています。

この例では、プロパティの初期化を厳格にチェックする設定や、未使用の変数や引数を許容しない設定が含まれています。

❶strictPropertyInitialization

このオプションをtrueに設定すると、クラスのプロパティがコンストラクタ内で正しく初期化されているかを厳格にチェックします。

これにより、アクセス修飾子privateprotectedを持つプロパティが適切に初期化されているかのチェックを強化することができます。

❷noUnusedParameters

このオプションをtrueにすると、関数やメソッドの引数で使用されていないものがある場合、コンパイルエラーが発生します。

これにより、不要な引数を持つメソッドや関数を未然に防ぐことが可能です。

❸noUnusedLocals

このオプションをtrueにすると、ローカル変数や定数が使用されていない場合、エラーが発生します。

これにより、不要な変数を持つことのリスクを減少させることができます。

まとめ

TypeScriptでのアクセス修飾子は、安全で読みやすいコードを記述する上で不可欠な要素となっています。

この記事を通じて、その基本的な使い方や応用例、さらにはカスタマイズ方法まで、TypeScriptのアクセス修飾子に関する多岐にわたる情報を習得することができました。

TypeScriptのアクセス修飾子を用いたプログラミングは、コードの品質向上だけでなく、開発者自身のスキルアップにも寄与します。

今後もアクセス修飾子の知識を深め、日々の開発活動に活かしていくことをおすすめします。