読み込み中...

TypeScriptで内部クラスを使う方法と応用10選

TypeScriptでの内部クラスの詳細な解説イメージ TypeScript
この記事は約29分で読めます。

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

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

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

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

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

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

はじめに

TypeScriptは、JavaScriptのスーパーセットとして人気を博しており、静的型チェックや強化されたオブジェクト指向の特性を持っています。

その中で、TypeScriptの「内部クラス」は、多くのプログラマーにとって重要な概念となっています。

内部クラスは、外部クラスの中に定義されるクラスであり、外部クラスのメンバーへのアクセスが可能な一方、外部からはアクセスできない特性を持っています。

この記事では、TypeScriptでの内部クラスの使い方とその応用方法を、10のサンプルコードを交えて解説します。

実践的な知識を得ることで、あなたのコードのクオリティを更に向上させることができるでしょう。

●TypeScriptの内部クラスとは?

TypeScriptの内部クラスとは、あるクラスの中に定義される別のクラスを指します。

これにより、外部からはアクセスできない、カプセル化されたデータやメソッドを持つことができます。

内部クラスは、オブジェクト指向プログラミングの中で非常に有効な手段として活用されています。

●内部クラスの詳細な使い方

ここで、内部クラスの具体的な使い方をサンプルコードとともに説明します。

○サンプルコード1:基本的な内部クラスの定義方法

このコードでは、外部クラス「OuterClass」の中に、内部クラス「InnerClass」を定義しています。

この例では、外部クラスの中で内部クラスをインスタンス化して利用しています。

class OuterClass {
    private outerValue: number;

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

    // 内部クラスの定義
    class InnerClass {
        public innerValue: string;

        constructor(innerVal: string) {
            this.innerValue = innerVal;
        }

        display(): void {
            console.log(`Outer Value: ${this.outerValue}, Inner Value: ${this.innerValue}`);
        }
    }

    useInner(): void {
        const inner = new this.InnerClass("innerData");
        inner.display();
    }
}

const instance = new OuterClass(10);
instance.useInner();

このコードを実行すると、コンソールに「Outer Value: 10, Inner Value: innerData」と表示されます。

○サンプルコード2:内部クラスから外部クラスのメンバへのアクセス

このコードでは、内部クラスから外部クラスのメンバーへアクセスする方法を表しています。

この例では、内部クラスのメソッドから外部クラスのプライベートメンバーを参照しています。

class OuterClass2 {
    private outerValue: number = 20;

    class InnerClass2 {
        display(): void {
            console.log(`Accessing outer class member from inner class: ${this.outerValue}`);
        }
    }

    useInner(): void {
        const inner = new this.InnerClass2();
        inner.display();
    }
}

const instance2 = new OuterClass2();
instance2.useInner();

このコードを実行すると、コンソールに「Accessing outer class member from inner class: 20」と表示されることを確認できます。

○サンプルコード3:内部クラスを利用したカプセル化

このコードでは、内部クラスを使用して外部からアクセスできないデータをカプセル化する方法を表しています。

この例では、外部から直接アクセスできない内部データを保護しています。

class CapsuleClass {
    class CapsuleInner {
        private secretData: string = "Secret Message";

        reveal(): void {
            console.log(`Revealed: ${this.secretData}`);
        }
    }

    showSecret(): void {
        const capsule = new this.CapsuleInner();
        capsule.reveal();
    }
}

const capsuleInstance = new CapsuleClass();
capsuleInstance.showSecret();

このコードを実行すると、コンソールに「Revealed: Secret Message」と表示されることを確認できます。

●内部クラスの応用例

TypeScriptの内部クラスは、単なるクラス定義以上の機能を持っています。

多様な場面での活用が考えられるこの機能を、具体的なサンプルコードとともに見ていきましょう。

○サンプルコード4:内部クラスを使ったイベントリスナーの管理

このコードではイベントリスナーの管理を内部クラスを用いて行います。

この例では外部クラスがイベントの発行者となり、内部クラスがリスナーとして機能します。

class EventPublisher {
    private listeners: Array<Listener> = [];

    // 内部クラスとしてListenerを定義
    class Listener {
        constructor(private callback: Function) {}
        listen(data: any) {
            this.callback(data);
        }
    }

    addListener(callback: Function) {
        const listener = new this.Listener(callback);
        this.listeners.push(listener);
    }

    emit(data: any) {
        for (const listener of this.listeners) {
            listener.listen(data);
        }
    }
}

const publisher = new EventPublisher();
publisher.addListener(data => {
    console.log(`Received data: ${data}`);
});
publisher.emit("Hello, TypeScript!");

上記のコードを実行すると、「Received data: Hello, TypeScript!」というメッセージが表示されます。

○サンプルコード5:内部クラスとジェネリクスの組み合わせ

このコードではジェネリクスを使用して、任意のデータ型を持つ内部クラスを生成します。

この例では、外部クラスがデータを格納し、内部クラスがそのデータを処理する役割を持っています。

class DataProcessor<T> {
    class Processor {
        process(data: T) {
            // ここでデータの加工を行う
            return data;
        }
    }

    constructor(private data: T) {}

    run() {
        const processor = new this.Processor();
        return processor.process(this.data);
    }
}

const numberProcessor = new DataProcessor<number>(10);
console.log(numberProcessor.run()); // 10と表示される

数値10をデータとして持つDataProcessorのインスタンスを生成し、そのrunメソッドを実行すると、10がそのまま表示されます。

○サンプルコード6:デザインパターンにおける内部クラスの活用

このコードでは、Factoryパターンを実装する際の補助クラスとして、内部クラスを活用します。

この例では、外部クラスがファクトリとして機能し、内部クラスが具体的な製品を表現します。

class ToyFactory {
    class CarToy {
        play() {
            console.log("Zooming around with the car toy!");
        }
    }

    class DollToy {
        play() {
            console.log("Playing with the lovely doll!");
        }
    }

    produceToy(type: string) {
        if (type === "car") {
            return new this.CarToy();
        } else if (type === "doll") {
            return new this.DollToy();
        }
    }
}

const factory = new ToyFactory();
const toy = factory.produceToy("car");
toy.play(); // Zooming around with the car toy!と表示される

ファクトリから「car」というタイプのおもちゃを生産し、そのplayメソッドを実行すると、車のおもちゃで遊ぶ様子を表すメッセージが表示されます。

○サンプルコード7:内部クラスを使用した状態の管理

TypeScriptの内部クラスの魅力的な利用例の一つに、状態の管理が挙げられます。

特に、アプリケーションが大規模になると、状態管理の複雑さは増します。

しかし、内部クラスを活用することで、状態の管理をより簡潔かつ効率的に行うことが可能となります。

このコードでは、外部クラスUserInfoがユーザーの情報を保持し、その状態を内部クラスUserStatusを通じて管理する例を表しています。

この例では、ユーザーのアクティブ状態やオンライン状態を管理しています。

class UserInfo {
    name: string;
    private status: UserStatus;

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

    // UserStatusにアクセスするメソッド
    goOnline() {
        this.status.setActive(true);
    }

    goOffline() {
        this.status.setActive(false);
    }

    isUserActive(): boolean {
        return this.status.isActive();
    }

    // 内部クラスの定義
    private class UserStatus {
        private active: boolean = false;

        setActive(state: boolean) {
            this.active = state;
        }

        isActive(): boolean {
            return this.active;
        }
    }
}

// 使用例
const user = new UserInfo("Taro");
user.goOnline();

このコードでは、外部クラスのメンバ変数やメソッドを通じて内部クラスの状態を変更・参照することができます。

このように内部クラスを活用することで、関連する情報や操作を一箇所にまとめ、コードの見通しを良くすることができます。

上記のコードを実行すると、Taroという名前のUserInfoオブジェクトが生成され、そのユーザーの状態をオンラインに変更する操作を行います。

このように、外部からは内部クラスの詳細を意識せずに状態管理が行えるのが大きな利点と言えるでしょう。

○サンプルコード8:モジュール内の内部クラスの役割

TypeScriptでは、モジュール内部でクラスを定義することにより、特定の機能や構造をモジュールの外部から隠蔽することができます。

このように、モジュール内で定義されたクラスは、他のモジュールから直接アクセスすることができないため、安全にコードを構築する際の手段として利用されます。

このコードでは、モジュール内で内部クラスを定義し、その役割と使い方について表しています。

この例では、外部からアクセスできない内部クラスをモジュール内で定義し、その内部クラスを利用してデータを管理しています。

// module.ts
module InternalClassModule {
    // 内部クラスの定義
    class InternalClass {
        constructor(private message: string) {}
        printMessage() {
            console.log(this.message);
        }
    }

    // 内部クラスのインスタンス化
    const internalInstance = new InternalClass("Hello from the internal class!");

    export function showMessage() {
        internalInstance.printMessage();
    }
}

// main.ts
import { showMessage } from "./module";

showMessage();  // "Hello from the internal class!" と表示されます。

上記のサンプルコードでは、InternalClassModuleというモジュール内で、InternalClassというクラスを定義しています。

このInternalClassは、モジュールの外部からはアクセスできないようになっており、その安全性を高めています。

しかし、showMessage関数をexportすることで、外部からこの関数を利用することは可能です。

○サンプルコード9:内部クラスと非同期処理の連携

TypeScriptの内部クラスは、独立した役割を持たせたい場合や、外部からのアクセスを制限したい場合に有効です。

ここでは、非同期処理を伴う場合に内部クラスをどのように活用できるかに焦点を当てて解説します。

このコードでは、非同期処理の結果を管理する外部クラスと、非同期処理自体を実行する内部クラスを組み合わせています。

この例では、外部クラスが非同期処理の進行状況や結果を取得・管理し、内部クラスが実際の非同期処理を実施しています。

class AsyncManager {
    private asyncProcessor: AsyncProcessor;

    constructor() {
        this.asyncProcessor = new AsyncProcessor(this);
    }

    async fetchAndProcessData() {
        const data = await this.asyncProcessor.fetchData();
        // 何らかのデータの加工や後続の処理をここで行う
        console.log(data);
    }

    private class AsyncProcessor {
        private manager: AsyncManager;

        constructor(manager: AsyncManager) {
            this.manager = manager;
        }

        async fetchData(): Promise<string> {
            // 何らかの非同期処理を行い、データを取得する
            // ここでは例としてsetTimeoutを使ってシミュレーションします
            return new Promise((resolve) => {
                setTimeout(() => {
                    resolve("非同期で取得したデータ");
                }, 2000);
            });
        }
    }
}

const manager = new AsyncManager();
manager.fetchAndProcessData();

この例で、AsyncManagerクラスが非同期処理の進行状況や結果を管理する役割を持ちます。

一方で、AsyncProcessor内部クラスは、実際の非同期処理を担当します。

内部クラスを使用することで、非同期処理自体を外部から隠蔽し、AsyncManagerクラスのみを公開することができます。

実際に上記のコードを実行すると、2秒後に”非同期で取得したデータ”という文字列がコンソールに表示されることが確認できます。

このように、外部クラスと内部クラスの連携をうまく活用することで、非同期処理の管理やデータ取得の処理を効率的に実装することが可能となります。

注意点としては、非同期処理のエラーハンドリングを適切に行う必要があります。

非同期処理が失敗した場合や、取得したデータが期待するものでなかった場合の処理もしっかりと実装することで、より堅牢なシステムを構築することができます。

次に、非同期処理のカスタマイズの一例として、タイムアウトを設定する方法を表します。

// ... (上のコードと同じ部分)

class AsyncManager {
    // ...
    async fetchAndProcessDataWithTimeout(timeout: number) {
        try {
            const data = await this.withTimeout(this.asyncProcessor.fetchData(), timeout);
            console.log(data);
        } catch (error) {
            console.log("タイムアウトまたはエラーが発生しました。");
        }
    }

    private withTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {
        let timer: NodeJS.Timeout;
        const timeoutPromise = new Promise<T>((_, reject) => {
            timer = setTimeout(() => {
                reject(new Error("タイムアウト"));
            }, timeout);
        });
        return Promise.race([promise.finally(() => clearTimeout(timer)), timeoutPromise]);
    }
}

const managerWithTimeout = new AsyncManager();
managerWithTimeout.fetchAndProcessDataWithTimeout(1000);  // 1秒でタイムアウト

このカスタマイズ例では、非同期処理にタイムアウトを設定する方法を表しています。

タイムアウトの設定は、外部APIの呼び出しや大量のデータの処理など、応答が遅くなる可能性のある場面で有効です。

○サンプルコード10:内部クラスを活用したエラーハンドリング

エラーハンドリングは、アプリケーションの安全性やユーザーエクスペリエンスを向上させるために重要です。

TypeScriptでの内部クラスを使用すると、エラーハンドリングをより効率的に、かつエラーロジックをきれいにカプセル化して実装することができます。

下記のコードでは、エラーの発生源となる非同期のタスクを実行する内部クラスと、そのエラーをハンドリングする外部クラスの連携を紹介しています。

この例では、非同期タスクが成功した場合、成功したデータを返します。

失敗した場合は、エラー情報をキャッチして適切なメッセージを表示します。

class ErrorHandler {
    private taskExecutor: TaskExecutor;

    constructor() {
        this.taskExecutor = new TaskExecutor();
    }

    async executeTask() {
        try {
            const result = await this.taskExecutor.runTask();
            console.log(`タスクが成功しました: ${result}`);
        } catch (error) {
            console.log(`エラーが発生しました: ${error.message}`);
        }
    }

    private class TaskExecutor {
        async runTask(): Promise<string> {
            // 例として50%の確率でエラーをスローする非同期タスクを実行します
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    Math.random() > 0.5 ? resolve('成功') : reject(new Error('何らかのエラー'));
                }, 1000);
            });
        }
    }
}

const handler = new ErrorHandler();
handler.executeTask();

このコードでは、TaskExecutor内部クラスが非同期タスクの実行を担当しています。

この非同期タスクは、単純な確率(50%)で成功またはエラーを返します。

一方、ErrorHandlerクラスでは、タスクの結果に基づいて成功メッセージまたはエラーメッセージをコンソールに表示します。

上記のコードを実行すると、50%の確率で「タスクが成功しました: 成功」と表示され、残りの50%の確率で「エラーが発生しました: 何らかのエラー」と表示されます。

●注意点と対処法

TypeScriptで内部クラスを使用する際、初心者から中級者までが気をつけるべきいくつかの注意点が存在します。

これらのポイントを知っておくことで、コードの安定性や保守性を高めることができます。

ここでは、それらの注意点と対処法について詳しく取り上げます。

○内部クラスのスコープの理解

このコードでは、外部クラスから内部クラスのメンバにアクセスしようとする例を表しています。

この例では、外部クラスから内部クラスのプライベートメンバにアクセスしようとしています。

class OuterClass {
    class InnerClass {
        private innerValue: number = 100;
    }

    constructor() {
        let inner = new this.InnerClass();
        // console.log(inner.innerValue); // エラー: 'innerValue'はプライベートであり、クラス 'InnerClass' 内でのみアクセスできます
    }
}

このように、外部クラスから内部クラスのプライベートメンバにアクセスしようとすると、エラーが発生します。

これは、内部クラスのスコープに関する基本的なルールに従っています。

対処法としては、アクセスしたいメンバが外部クラスからも利用可能である場合は、publicprotectedなどのアクセス修飾子を使用することで対応できます。

○内部クラスのインスタンス化タイミング

このコードでは、内部クラスを外部クラスのコンストラクタでインスタンス化する方法を表しています。

この例では、外部クラスのコンストラクタ内で内部クラスをインスタンス化していますが、そのタイミングが非常に重要です。

class OuterClass {
    class InnerClass {
        constructor() {
            console.log("InnerClassのインスタンスが作成されました");
        }
    }

    constructor() {
        console.log("OuterClassのインスタンスが作成される前");
        let inner = new this.InnerClass();
        console.log("OuterClassのインスタンスが作成された後");
    }
}

let outer = new OuterClass();

この場合、次のような出力が得られます。

OuterClassのインスタンスが作成される前
InnerClassのインスタンスが作成されました
OuterClassのインスタンスが作成された後

内部クラスのインスタンス化のタイミングを理解しておくことは、不要なバグを避けるために重要です。

○内部クラスの継承に関する制約

内部クラスは、他のクラスを継承することができますが、その際の制約も理解しておく必要があります。

このコードでは、内部クラスが別のクラスを継承している例を表しています。

この例では、内部クラスがBaseClassを継承しているシチュエーションを想定しています。

class BaseClass {
    protected baseValue: number = 200;
}

class OuterClass {
    class InnerClass extends BaseClass {
        showValue() {
            console.log(this.baseValue); // BaseClassのbaseValueを使用
        }
    }
}

このように、内部クラスは他のクラスを継承することができます。

ただし、継承先のクラスのアクセス制約なども適切に理解しておく必要があります。

●カスタマイズ方法

TypeScriptの内部クラスは、その特性と機能により、多くのカスタマイズが可能となっています。

ここでは、内部クラスをカスタマイズする際の一般的なアプローチと具体的なサンプルコードを提供し、さらに深い理解と応用を得る手助けとします。

○サンプルコード11:属性の変更による内部クラスのカスタマイズ

このコードでは、内部クラスの属性を動的に変更して、その動作をカスタマイズする方法を表しています。

この例では、内部クラスの属性を外部から変更して、異なる動作を実現しています。

class OuterClass {
    class InnerClass {
        attribute: string;

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

        displayAttribute() {
            console.log(`現在の属性: ${this.attribute}`);
        }
    }
}

let instance = new OuterClass.InnerClass("初期属性");
instance.displayAttribute(); // 現在の属性: 初期属性

instance.attribute = "変更後の属性";
instance.displayAttribute(); // 現在の属性: 変更後の属性

上記のコードにおいて、外部クラスから内部クラスの属性attributeを直接変更しています。

このように、外部からアクセス可能な属性を持つ内部クラスを設計することで、動的なカスタマイズが可能となります。

実際にコードを実行すると、まず”初期属性”と表示され、次に”変更後の属性”と表示されることが確認できます。

○サンプルコード12:メソッドのオーバーライドによるカスタマイズ

このコードでは、内部クラスのメソッドをオーバーライドすることで、その動作をカスタマイズする方法を表しています。

この例では、継承を利用して内部クラスのメソッドを再定義し、新しい動作を追加しています。

class OuterClass {
    class InnerClass {
        displayMessage(): void {
            console.log("InnerClassのメッセージ");
        }
    }

    class CustomizedInnerClass extends InnerClass {
        // メソッドをオーバーライド
        displayMessage(): void {
            console.log("カスタマイズされたInnerClassのメッセージ");
        }
    }
}

let originalInstance = new OuterClass.InnerClass();
originalInstance.displayMessage(); // InnerClassのメッセージ

let customizedInstance = new OuterClass.CustomizedInnerClass();
customizedInstance.displayMessage(); // カスタマイズされたInnerClassのメッセージ

上記のコードにおいて、CustomizedInnerClassでは、InnerClassdisplayMessageメソッドをオーバーライドしています。

これにより、オリジナルの動作に加えて新しい動作を実装することができます。

コードを実行すると、”InnerClassのメッセージ”というメッセージの後に、”カスタマイズされたInnerClassのメッセージ”と表示されることが確認できます。

これらのカスタマイズ方法は、アプリケーションの要件やビジネスロジックに合わせて、内部クラスの振る舞いを柔軟に変更するためのベースとなります。

開発者はこれらの基本的なアプローチを基に、独自のカスタマイズを行っていくことが期待されます。

●カスタマイズ方法

TypeScriptの内部クラスは、その特性と機能により、多くのカスタマイズが可能となっています。

このセクションでは、内部クラスをカスタマイズする際の一般的なアプローチと具体的なサンプルコードを提供し、さらに深い理解と応用を得る手助けとします。

○属性の変更による内部クラスのカスタマイズ

このコードでは、内部クラスの属性を動的に変更して、その動作をカスタマイズする方法を表しています。

この例では、内部クラスの属性を外部から変更して、異なる動作を実現しています。

class OuterClass {
    class InnerClass {
        attribute: string;

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

        displayAttribute() {
            console.log(`現在の属性: ${this.attribute}`);
        }
    }
}

let instance = new OuterClass.InnerClass("初期属性");
instance.displayAttribute(); // 現在の属性: 初期属性

instance.attribute = "変更後の属性";
instance.displayAttribute(); // 現在の属性: 変更後の属性

上記のコードにおいて、外部クラスから内部クラスの属性attributeを直接変更しています。

このように、外部からアクセス可能な属性を持つ内部クラスを設計することで、動的なカスタマイズが可能となります。

実際にコードを実行すると、まず”初期属性”と表示され、次に”変更後の属性”と表示されることが確認できます。

○メソッドのオーバーライドによるカスタマイズ

このコードでは、内部クラスのメソッドをオーバーライドすることで、その動作をカスタマイズする方法を表しています。

この例では、継承を利用して内部クラスのメソッドを再定義し、新しい動作を追加しています。

class OuterClass {
    class InnerClass {
        displayMessage(): void {
            console.log("InnerClassのメッセージ");
        }
    }

    class CustomizedInnerClass extends InnerClass {
        // メソッドをオーバーライド
        displayMessage(): void {
            console.log("カスタマイズされたInnerClassのメッセージ");
        }
    }
}

let originalInstance = new OuterClass.InnerClass();
originalInstance.displayMessage(); // InnerClassのメッセージ

let customizedInstance = new OuterClass.CustomizedInnerClass();
customizedInstance.displayMessage(); // カスタマイズされたInnerClassのメッセージ

上記のコードにおいて、CustomizedInnerClassでは、InnerClassdisplayMessageメソッドをオーバーライドしています。

これにより、オリジナルの動作に加えて新しい動作を実装することができます。

コードを実行すると、”InnerClassのメッセージ”というメッセージの後に、”カスタマイズされたInnerClassのメッセージ”と表示されることが確認できます。

まとめ

TypeScriptでの内部クラスの利用は、コードの組織化やカプセル化の強化など、多くの利点をもたらすものです。

この記事で取り上げたサンプルコードを通じて、その機能と応用例を理解することができました。

TypeScriptの内部クラスは非常に強力なツールであり、上手く活用することで、より質の高いコードを書く手助けとなるでしょう。

今回学んだ知識をベースに、さらに実践的な経験を積み重ねて、TypeScriptのエキスパートを目指してください。