はじめに
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
というクラスにname
とbirthYear
という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修飾子を使った継承の例を表しています。
この例では、BaseClass
のbaseValue
フィールドを継承先の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 と表示される
上記のコードを実行すると、DerivedClass
がBaseClass
を継承しているため、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 [];
}
}
上記のコードを利用する際、使用者はisConnected
やconnect()
、fetchData(query: string)
のみを利用できます。
内部で使用されるconnection
やinitConnection()
は、外部からアクセスすることができません。
これにより、ライブラリの安全性が高まり、意図しない操作やアクセスを防ぐことができます。
外部からこのライブラリを使用する場合、次のようになります。
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
に設定すると、クラスのプロパティがコンストラクタ内で正しく初期化されているかを厳格にチェックします。
これにより、アクセス修飾子private
やprotected
を持つプロパティが適切に初期化されているかのチェックを強化することができます。
❷noUnusedParameters
このオプションをtrue
にすると、関数やメソッドの引数で使用されていないものがある場合、コンパイルエラーが発生します。
これにより、不要な引数を持つメソッドや関数を未然に防ぐことが可能です。
❸noUnusedLocals
このオプションをtrue
にすると、ローカル変数や定数が使用されていない場合、エラーが発生します。
これにより、不要な変数を持つことのリスクを減少させることができます。
まとめ
TypeScriptでのアクセス修飾子は、安全で読みやすいコードを記述する上で不可欠な要素となっています。
この記事を通じて、その基本的な使い方や応用例、さらにはカスタマイズ方法まで、TypeScriptのアクセス修飾子に関する多岐にわたる情報を習得することができました。
TypeScriptのアクセス修飾子を用いたプログラミングは、コードの品質向上だけでなく、開発者自身のスキルアップにも寄与します。
今後もアクセス修飾子の知識を深め、日々の開発活動に活かしていくことをおすすめします。