TypeScriptでプロパティを隠蔽する3つの方法:プライベートセッター、readonly、デコレータ
TypeScriptにおけるプライベートセッター、ゲッター、アクセサの解説
TypeScriptでは、クラスのプロパティのアクセス制御を強化するために、アクセサと呼ばれる機能を提供しています。アクセサは、プロパティのゲッターとセッターを定義することで、プロパティの読み書き方法を制御できます。
ゲッターは、プロパティの値を取得するために使用されます。一方、セッターは、プロパティの値を設定するために使用されます。アクセサは、プロパティ名の前にgetまたはsetキーワードを付けて定義します。
プライベートセッターは、セッターをprivateキーワードで修飾することで実現できます。これにより、セッターはクラス内からのみ呼び出すことができ、外部からのプロパティ値の直接変更を防止できます。
例
class Person {
private _name: string;
public get name(): string {
return this._name;
}
public set name(newName: string) {
// 新しい名前を検証するロジック
if (!newName.trim()) {
throw new Error("名前は空文字にしてはいけません。");
}
this._name = newName;
}
}
この例では、Person
クラスは_name
というプライベートプロパティを持ちます。このプロパティは、name
というゲッターとセッターを通してのみアクセスできます。セッターはプライベートなので、Person
クラスの外からは呼び出すことができません。
アクセサの利点
- ロジックの追加: プロパティの読み書き処理にロジックを追加できます。
- 検証: プロパティに設定される値を検証し、不正な値を排除できます。
- カプセル化: プロパティの内部実装を隠蔽し、データの整合性を保ちます。
- プライベートセッターを使用すると、デバッグが難しくなる場合があります。
- アクセサを使用すると、コードが冗長になる場合があります。
プライベートセッターは、TypeScriptにおける強力な機能ですが、使いどころを誤るとコードが複雑になる可能性があります。アクセサを使用する前に、そのメリットとデメリットを慎重に検討することが重要です。
- アクセサは、デコレータを使用してさらに拡張できます。
- TypeScriptには、
readonly
キーワードを使用してプロパティを書き込み不可にする機能もあります。
class Employee {
private _id: number;
private _name: string;
private _department: string;
private _salary: number;
constructor(id: number, name: string, department: string, salary: number) {
this._id = id;
this._name = name;
this._department = department;
this._salary = salary;
}
public get id(): number {
return this._id;
}
public get name(): string {
return this._name;
}
public set name(newName: string) {
if (!newName.trim()) {
throw new Error("名前は空文字にしてはいけません。");
}
this._name = newName;
}
public get department(): string {
return this._department;
}
public set department(newDepartment: string) {
if (!newDepartment.trim()) {
throw new Error("部署は空文字にしてはいけません。");
}
this._department = newDepartment;
}
public get salary(): number {
return this._salary;
}
private set salary(newSalary: number) {
if (newSalary < 0) {
throw new Error("給与は負の値にはできません。");
}
this._salary = newSalary;
}
public raiseSalary(rate: number): void {
this.salary = this.salary * (1 + rate);
}
}
説明
このコードは、以下の機能を持つEmployee
クラスを実装しています。
raiseSalary
メソッドを使用して、給与を昇給させる- 給与のプロパティにプライベートセッターを設定し、負の値を設定できないようにする
- 名前と部署のプロパティにプライベートセッターを設定し、値の検証を行う
- 社員ID、名前、部署、給与を管理するプロパティを持つ
使い方
const employee = new Employee(1, "山田太郎", "営業部", 300000);
console.log(`社員ID: ${employee.id}`); // 社員ID: 1
console.log(`名前: ${employee.name}`); // 名前: 山田太郎
console.log(`部署: ${employee.department}`); // 部署: 営業部
console.log(`給与: ${employee.salary}`); // 給与: 300000
employee.name = "佐藤次郎"; // 名前を変更
console.log(`名前: ${employee.name}`); // 名前: 佐藤次郎
employee.department = ""; // 部署を空文字に設定しようとするとエラー
// Error: 部署は空文字にしてはいけません。
employee.salary = -10000; // 給与を負の値に設定しようとするとエラー
// Error: 給与は負の値にはできません。
employee.raiseSalary(0.1); // 給与を10%昇給
console.log(`給与: ${employee.salary}`); // 給与: 330000
ポイント
raiseSalary
メソッドを使用して、給与を昇給するロジックを追加しています。- アクセサを使用して、プロパティの読み書きをカプセル化しています。
- プライベートセッターを使用して、プロパティ値の検証と制御を行っています。
プライベートセッター以外の方法
readonlyキーワード
readonly
キーワードを使用して、プロパティを書き込み不可にすることができます。これにより、プロパティの値は一度設定されると変更できなくなります。
class Employee {
private _id: number;
private readonly _name: string;
private readonly _department: string;
private _salary: number;
constructor(id: number, name: string, department: string, salary: number) {
this._id = id;
this._name = name;
this._department = department;
this._salary = salary;
}
public get id(): number {
return this._id;
}
public get name(): string {
return this._name;
}
public get department(): string {
return this._department;
}
public get salary(): number {
return this._salary;
}
}
アクセサなしのプロパティ
アクセサを使用せずに、プロパティをプライベートにすることもできます。ただし、この方法ではプロパティの読み書きを制御することはできません。
class Employee {
private _id: number;
private _name: string;
private _department: string;
private _salary: number;
constructor(id: number, name: string, department: string, salary: number) {
this._id = id;
this._name = name;
this._department = department;
this._salary = salary;
}
}
デコレータ
デコレータを使用して、プロパティのアクセス制御を拡張することもできます。デコレータは、クラスやプロパティに装飾を追加するための機能です。
class Employee {
private _id: number;
private _name: string;
private _department: string;
private _salary: number;
constructor(id: number, name: string, department: string, salary: number) {
this._id = id;
this._name = name;
this._department = department;
this._salary = salary;
}
@validateName
public get name(): string {
return this._name;
}
@validateDepartment
public get department(): string {
return this._department;
}
@validateSalary
public get salary(): number {
return this._salary;
}
}
function validateName(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalGetter = descriptor.get;
descriptor.get = function(): string {
const value = originalGetter.apply(this);
if (!value.trim()) {
throw new Error("名前は空文字にしてはいけません。");
}
return value;
};
}
function validateDepartment(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalGetter = descriptor.get;
descriptor.get = function(): string {
const value = originalGetter.apply(this);
if (!value.trim()) {
throw new Error("部署は空文字にしてはいけません。");
}
return value;
};
}
function validateSalary(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalGetter = descriptor.get;
descriptor.get = function(): number {
const value = originalGetter.apply(this);
if (value < 0) {
throw new Error("給与は負の値にはできません。");
}
return value;
};
}
それぞれの方法の利点と欠点
方法 | 利点 | 欠点 |
---|---|---|
プライベートセッター | プロパティの値を検証および制御できる | コードが冗長になる可能性がある |
readonlyキーワード | プロパティの値を書き込み不可にできる | プロパティの値を変更できない |
アクセサなしのプロパティ | コードがシンプルになる | プロパティの読み書きを制御できない |
デコレータ | プロパティのアクセス制御を柔軟に拡張できる | コードが複雑になる可能性がある |
typescript setter getter-setter