はじめに
TypeScriptは、大規模なアプリケーション開発や、安全なコーディングを強化するために生まれたJavaScriptのスーパーセットです。
TypeScriptの特長の一つである「extends」について、今回は詳しく解説いたします。
この記事を通して、TypeScript初心者でも「extends」の驚きの使い方や応用方法を簡単に理解し、実際のコーディングに活かせるようになります。
●TypeScriptとextendsの基本
TypeScriptは、単にJavaScriptを拡張するだけでなく、安全でスケーラブルなコードベースの構築をサポートするために、静的型付けやクラスベースのオブジェクト指向プログラミングを可能にする強力な機能を提供します。
この記事では、そんなTypeScriptの中核を成す概念である「継承」を解き明かします。
extends キーワードを用いることで具体化されるこの機能は、コードの再利用や拡張が容易になるため、開発効率と保守性を大幅に向上させることができるのです。
それでは、具体的な使い方を見ていきましょう。
○TypeScriptとは?
TypeScriptは、Microsoftが開発したJavaScriptのスーパーセットとして知られています。
JavaScriptのコードはそのままTypeScriptとして動作するため、JavaScriptの知識がある方はスムーズに移行できます。
しかし、TypeScriptは静的型付けを提供することで、コンパイル時に型のエラーを検出することができる最大の利点があります。
これにより、ランタイム時のエラーを大幅に削減し、品質の高いコードを書くことが可能となります。
○extendsの役割と基本的な使い方
TypeScriptでの「extends」は、主にクラスの継承に使用されるキーワードです。
このキーワードを用いることで、既存のクラスからプロパティやメソッドを受け継ぎながら、新たなクラスを作成することができます。
例を見てみましょう。
このコードでは、Animalクラスを基にしたDogクラスを作成しています。
そして、DogクラスはAnimalクラスからmoveメソッドを受け継ぎつつ、新たにbarkメソッドを持つようになっています。
このように、extendsを使うことで、既存のクラスの機能を再利用しつつ、新しい機能を追加したクラスを効率よく定義することができます。
このコードを実行すると、まずmoveメソッドにより、「ポチは移動します。」と表示され、次にbarkメソッドにより、「ワンワン」と表示されます。
●extendsの使い方
TypeScriptにおけるextendsキーワードは、クラスベースのオブジェクト指向プログラムにおける継承をサポートします。
継承とは、既存のクラスから新しいクラスを作成することで、新しいクラスは元のクラスの属性やメソッドを継承します。
これにより、コードの再利用性が向上し、新しいクラスに追加的な機能や属性を追加することが容易になります。
○サンプルコード1:基本的なクラスの継承
TypeScriptにおける基本的なクラス継承の方法を紹介します。
下記のコードは、動物を表す基本クラスAnimalと、Animalを継承して特定の動物、犬を表すDogクラスを定義しています。
このコードでは、Animalクラスを定義しており、それに名前を持つ動物の一般的な動作を示すspeakメソッドがあります。
Dogクラスは、このAnimalクラスを継承し、犬特有の鳴き声を返すbarkメソッドを持っています。
このコードを実行すると、犬のオブジェクトmyDogはAnimalから継承されたspeakメソッドと、Dogクラス独自のbarkメソッドの両方を使用することができます。
結果、次の出力が得られます。
○サンプルコード2:抽象クラスを継承して具体的な実装を追加
抽象クラスとは、具体的な実装がされていないメソッドやプロパティを持つことができる特別なクラスのことを指します。
この抽象クラスを継承する際、子クラスではこれらの抽象メソッドやプロパティに具体的な実装を提供する必要があります。
TypeScriptで抽象クラスを定義し、それを継承して具体的な実装を追加するサンプルコードを紹介します。
このコードでは、抽象クラスAnimalを使って、動物が持っている共通の特徴や動作を定義しています。
具体的には、名前を表すnameという抽象プロパティや、動物が発する音を表すmakeSoundという抽象メソッドが定義されています。
次に、Dogクラスでは、Animalクラスを継承しており、nameプロパティやmakeSoundメソッドに具体的な実装を提供しています。
このコードを実行すると、Dogクラスのインスタンスdogを作成して、moveメソッドとmakeSoundメソッドを呼び出します。
その結果、コンソールには次の出力が表示されます。
○サンプルコード3:複数のインターフェースを継承
TypeScriptでは、複数のインターフェースを1つのクラスやインターフェースに継承することができます。
これは、JavaやC#などの他のオブジェクト指向言語でも一般的に見られる特徴ですが、TypeScriptでもこの特徴を効果的に活用することができます。
実際のサンプルコードを見てみましょう。
このコードでは、Runnableとflyableという2つのインターフェースを定義しています。
そして、SuperHeroクラスでは、これらのインターフェースをimplementsキーワードを使用して複数継承しています。
これにより、SuperHeroクラスはrunメソッドとflyメソッドの両方を持つことができます。
このコードを実行すると、次のような結果になります。
スーパーヒーローのインスタンスを作成して、そのメソッドを実行するコードを追記します。
○サンプルコード4:継承を使ったポリモーフィズム
TypeScriptの魅力的な機能の一つとして、JavaやC#などの他のオブジェクト指向プログラミング言語と同じように、ポリモーフィズムを実現することができます。
ここでは、TypeScriptを用いて、ポリモーフィズムを実現する方法について解説します。
まず、ポリモーフィズムとは何か、簡単に説明します。
ポリモーフィズムは、オブジェクト指向プログラミングの大事な概念の一つであり、異なるクラスのオブジェクトを、共通のインターフェースや親クラスを介して同一視して取り扱うことができる機能です。
これにより、異なるオブジェクトを一貫した方法で操作することができるのです。
TypeScriptでポリモーフィズムを実現するためのサンプルコードを紹介します。
このコードでは、Animalクラスを親クラスとして、その子クラスとしてDogクラスとCatクラスを定義しています。
それぞれの子クラスでは、親クラスのspeakメソッドをオーバーライドしており、動物に応じた音を返すようにしています。
コードを実行すると、それぞれの動物の特有の音、つまり”ワンワン”や”ニャー”といった結果が得られます。
このように、異なるクラスのオブジェクトでも、共通のインターフェースを持つ親クラスを介して、同じメソッドを呼び出すことができるのが、ポリモーフィズムの魅力です。
●extendsの応用例
TypeScriptの「extends」キーワードを一歩先に進めて考えると、さまざまな驚きの応用例が考えられます。
基本的な使い方だけでなく、より高度な型定義や設計の手法にも活用することができます。
今回は、その中から特に実践的な一例をご紹介いたします。
○サンプルコード5:ジェネリックスとextendsを組み合わせた高度な型定義
TypeScriptのジェネリックスを使用すると、型をパラメータ化して再利用性を高めることができます。
そして、このジェネリックスと「extends」を組み合わせることで、特定の型に制約を持たせた高度な型定義を行うことができます。
例として、特定のオブジェクトにnameプロパティが存在することを保証する型を定義してみましょう。
このコードでは、ジェネリックスTがnameプロパティを持つことを保証する型「HasName」を定義しています。
そして、personオブジェクトを定義する際にこの型を使用しているため、nameプロパティが必須となります。
このような方法で、ジェネリックスとextendsを組み合わせて型の安全性を向上させることができます。
このコードを実行すると、特にエラーは発生せず、personオブジェクトが正常に定義されます。
ただし、nameプロパティを省略すると、TypeScriptのコンパイラによりエラーが報告されることが予想されます。
このように、ジェネリックスとextendsを組み合わせることで、コードの品質や安全性を向上させることができます。
○サンプルコード6:Mixinを利用した複数のクラスの機能統合
TypeScriptでは、一つのクラスが複数の他のクラスの機能を取り込むための方法として、「Mixin」を利用することができます。
これは、JavaScriptやその他の多くの言語で見られるクラスベースの継承とは異なり、特定の機能だけを取り込むことができるため、より柔軟なコード構造を構築することができます。
ここで、Mixinの使い方を示す簡単なサンプルコードを紹介します。
このコードでは、Flyableという名前のMixinが飛べる動物の振る舞いを定義しています。
同様に、SwimmableというMixinは泳げる動物の振る舞いを定義しています。
そして、BirdクラスはFlyableを継承して飛ぶ機能を持つようにしています。
更に進めて、DuckクラスはFlyableを継承し、更にSwimmableの泳ぐ機能も取り込んでいます。
このサンプルコードを実行すると、新しく作成した鴨のインスタンスは飛びますが、それと同時に泳ぐ機能も持っているため、泳ぐ振る舞いもすることができます。
○サンプルコード7:条件付き型と組み合わせた型のカスタマイズ
TypeScriptには、型をより柔軟にカスタマイズする機能として「条件付き型」が提供されています。
条件付き型を使用することで、型の条件に基づいて異なる型を返すことが可能となります。
今回は、この条件付き型を「extends」キーワードと組み合わせたカスタマイズについて詳しく解説します。
まず、条件付き型の基本的な文法を確認しましょう。条件付き型は、T extends U ? X : Yの形式で書かれます。
ここで、TがUを継承している場合は型Xを、そうでない場合は型Yを返します。
下記のサンプルコードでは、条件付き型を使って、TypeOrString<T>という型を定義しています。
この型は、Tがstringを継承している場合はTを、そうでない場合はstringを返すというものです。
このコードでは、TypeOrString<number>はstring型として、TypeOrString<"hello">は文字列リテラル型の"hello"として推論されます。
理由は、numberはstringを継承していないのでstring型を返し、"hello"はstringを継承しているため"hello"型を返すからです。
さらに、この条件付き型を使って、もっと複雑な型のカスタマイズを行うこともできます。次のサンプルコードでは、DeepReadonly<T>という型を定義しています。
この型は、オブジェクトのすべてのプロパティを再帰的に読み取り専用にします。
上記のコードのDeepReadonly<T>は、オブジェクトの各プロパティを再帰的に読み取り専用にします。
DeepReadonly<Obj>を使用することで、Obj型のすべてのプロパティが読み取り専用になります。
そのため、コメントアウトされたobj.a.b = 456;のような変更操作を試みると、TypeScriptのコンパイラはエラーを報告します。
○サンプルコード8:ユーティリティ型と組み合わせた型の変換
TypeScriptは、JavaScriptに静的型を追加する強力な言語であり、その強力な型システムにより、高度な型操作が可能となっています。
ここでは、TypeScriptの「extends」キーワードと、TypeScriptが提供するユーティリティ型との組み合わせについて解説します。
ユーティリティ型は、TypeScriptで提供される既存の型を簡単に変更・変換するための型です。
例えば、Partial<T>, Readonly<T>, Pick<T, K>などがユーティリティ型の一例です。
これらを「extends」と組み合わせることで、さらに柔軟な型操作が可能になります。
下記のサンプルコードでは、ユーティリティ型と「extends」を使用して、特定の型から特定のプロパティを取り出す方法を表しています。
このコードでは、Todoというインターフェースを定義しています。
次に、Filterというジェネリックス型を定義しています。
この型は、指定された型Tから、指定された型Uに一致するプロパティのキーだけを取り出す役割を持っています。
最後に、このFilter型を使用して、Todoインターフェースの中でstring型のプロパティだけのキーを取得しています。
このコードを実行すると、StringKeys型はtitle | descriptionという型として推論されます。
これは、Todoインターフェースの中でstring型のプロパティであるtitleとdescriptionのキーだけが取り出されるためです。
○サンプルコード9:アクセス修飾子との組み合わせ
TypeScriptの「extends」を理解する上で、アクセス修飾子との組み合わせも非常に重要です。
アクセス修飾子は、クラスのプロパティやメソッドがどのようにアクセスされるかを制限する機能です。
具体的には「public」、「private」、「protected」などがあります。
では、アクセス修飾子と「extends」を組み合わせたとき、どのような挙動になるのでしょうか。
それを明らかにするためのサンプルコードを紹介します。
このコードでは、BaseClassという親クラスがあり、その中に「public」、「private」、「protected」の3つのアクセス修飾子を持つプロパティが定義されています。
そして、DerivedClassという子クラスがこの親クラスを継承しています。
showPropertiesというメソッド内で、継承したプロパティへのアクセスを試みています。
その結果、publicPropとprotectedPropはアクセス可能であり、値を正しく出力することができます。
しかし、privatePropはprivate修飾子によって、継承先のクラスからはアクセスできません。
このコードを実行すると、親クラスのpublicプロパティはアクセス可能であるため、「public」という文字列が表示されます。
一方、privatePropにはアクセスできないため、その部分はコメントアウトしています。
最後に、protectedPropに関しても正常にアクセスでき、「protected」という文字列が表示されます。
○サンプルコード10:オーバーライドを使ったメソッドの拡張
TypeScriptにおいて、クラス継承時に親クラスのメソッドを子クラスで上書きすることをオーバーライドと言います。
オーバーライドは、既存のメソッドの振る舞いを変更したり、新しい処理を追加する際に非常に役立ちます。
具体的な例として、動物クラスを持ち、それを継承する犬クラスを考えます。
動物クラスには「鳴く」というメソッドが定義されているとし、犬クラスではこの「鳴く」メソッドをオーバーライドして「ワンワン」と鳴くように拡張します。
このコードでは、Animalクラスに定義されているsoundメソッドを、Dogクラスでオーバーライドしています。
そのため、Dogクラスのインスタンスでsoundメソッドを呼び出すと、「ワンワン」という結果が返されます。
このコードを実行すると、”ワンワン”という文字列がコンソールに表示されます。
これはDogクラスにおいてオーバーライドしたsoundメソッドが呼ばれるためです。
●注意点と対処法
TypeScriptでextendsを用いる際、そのパワフルさを活かしながら、いくつかのトラブルや不具合を回避するための知識が求められます。
ここでは、extendsを利用する際の主な注意点と、それに対する対処法を詳しく解説します。
○継承の際のコンストラクタの注意点
TypeScriptでのクラスの継承時、特にconstructorに関しては注意が必要です。
親クラスがconstructorを持つ場合、子クラスもコンストラクタを持つ必要があります。
また、子クラスのconstructor内でsuper()を呼び出すことは必須です。
このコードでは、ChildクラスはParentクラスを継承しています。
Childクラスのconstructor内でsuper(name)を呼び出すことで、親クラスのconstructorが実行されます。
このコードを実行すると、Childクラスのインスタンスを作成した際に、名前と年齢が順にコンソールに表示されます。
○抽象クラスとの違い
TypeScriptには、実装が存在せず、派生クラスでの実装が必須とされるabstract classというものも存在します。
extendsを用いて継承する際、この抽象クラスと通常のクラスの違いを理解しておくことは非常に重要です。
このコードでは、AbstractClassはgreetという抽象メソッドを持つ抽象クラスです。
これを継承するConcreteClassは、この抽象メソッドgreetを具体的に実装しています。
このコードを実行すると、ConcreteClassのインスタンスを作成し、greetメソッドを呼び出すと、”Hello!”とコンソールに表示されます。
○循環参照の問題と解決策
TypeScriptでは、2つのクラスが互いに継承し合う、いわゆる循環参照が発生すると、コンパイルエラーが生じます。
これは、クラスの定義が完了する前に他のクラスがそのクラスを参照しようとすると、未定義の状態で参照されるためです。
この問題を解決するためには、継承関係を見直し、循環参照を回避する設計にすることが求められます。
また、モジュールの分割やインターフェースの使用を検討することで、問題を回避することも可能です。
例えば、次のようにインターフェースを利用して、循環参照を回避することができます。
このコードでは、IGreetインターフェースを実装することで、ClassAとClassBはそれぞれ独立してgreetメソッドを持つことができます。
循環参照のリスクを避けつつ、同じメソッドの実装を持つクラスを作成することができます。
●カスタマイズ方法
TypeScriptでは、コードの再利用性と拡張性を高めるための多くのテクニックがあります。
その中でも、extendsを使用して、既存のクラスやインターフェースを拡張する際のカスタマイズ方法について解説します。
○メソッドチェイニングを利用したフレキシブルな継承
メソッドチェイニングは、一つのメソッドの呼び出し結果に別のメソッドを連鎖的に呼び出すことを指します。
これを使うことで、TypeScriptの継承においても、より簡潔に、かつ、直感的にコードを書くことができます。
このコードでは、BaseClassにsetValとshowValのメソッドを定義しています。
これらのメソッドは、自身のインスタンスを返すことで、メソッドチェイニングを可能にしています。
ExtendedClassは、BaseClassを継承しており、さらに独自のメソッドsetAdditionalValとshowAdditionalValを持っています。
これらも同様にメソッドチェイニングが可能です。
上記のコードを実行すると、次のような出力が得られます。
このように、メソッドチェイニングを利用すれば、継承されたクラスのインスタンスメソッドを連鎖的に呼び出すことができ、コードの可読性や効率が向上します。
○Decoratorを用いた機能の追加
Decoratorは、TypeScriptでクラスやメソッド、プロパティに特定の動作や機能を追加するためのものです。
これを利用することで、継承したクラスに対して、特定の機能や動作を追加することが容易になります。
このコードでは、logというDecoratorを定義しています。
このDecoratorは、指定されたメソッドが呼び出される際に、メソッド名と引数をログとして出力します。
MyClassのmultiplyメソッドには、@logのDecoratorが適用されています。
上記のコードを実行すると、次のような出力が得られます。
まとめ
TypeScriptは、JavaScriptのスーパーセットとして、型安全性を提供する強力なツールです。
中でも、extendsキーワードは、クラスやインターフェースの継承を容易に行うことができる機能として、多くのプログラマーに支持されています。
この記事では、extendsの基本的な使い方から、応用例、注意点、そしてカスタマイズ方法まで、詳細に渡って解説を行いました。
extendsを使用することで、TypeScriptの持つ強力な型システムを活かし、より堅牢なプログラムを実現することができるのです。
この記事を参考に、TypeScriptのextendsを活用して、効果的なプログラミングを楽しんでください。


