クラス(Typescriptを学んでいこう #3)
第3回ではクラスについてです。
Class
オブジェクト指向プログラミングの概念をサポートする言語の機能です。クラスは、関連するデータと関数をひとまとめにしたもので、それによってコードをより構造化し、保守性を高めることができます。
他の言語にも存在するもので、new
キーワードを使ってクラスをインスタンス化して使用します。
下記のコードが一例です。
class Department {
name: string;
constructor(n: string) {
this.name = n;
}
describe() {
console.log(`Department: ${this.name}`);
}
}
const accounting = new Department('Accounting');
accounting.describe();
const accountingCopy = { describe: accounting.describe }
accountingCopy.describe(); // `Department: undefined`が出力される
this
this
キーワードはそのクラスそのものの参照です。
そのため、上記のコードでは、17行目で describe()
を呼んでいますが、describe()
の中の console.log(
の Department: ${this.name}
)this
は accounting
ではなく、accountingCopy
を指します。
accountingCopy
は describe
メソッドを持つただのオブジェクトです。
そのため、accountingCopy
には this.name
がセットされていないため、Department: undefined
が出力されます。
回避するためには呼び出し先の引数に this
を定義してあげます。
class Department {
name: string;
constructor(n: string) {
this.name = n;
}
describe(this: Department) { // thisを明示する
console.log(`Department: ${this.name}`);
}
}
const accounting = new Department('Accounting');
accounting.describe();
const accountingCopy = { describe: accounting.describe }
accountingCopy.describe(); // `Department: Accounting`が出力される
private
メソッドや変数に private
をつけると、そのクラスからしか参照ができなくなります。
何もつけない場合は、デフォルトで public
が設定されており、どこからでも参照できます。
class Department {
public name: string; // publicはデフォルトなのでつけなくてもOK
private employees: string[] = []; //privateをつけると他から参照できなくなる
constructor(lastName: string, employeesList: string[]) {
this.name = lastName;
this. employees = employeeList;
}
}
コンストラクタでの省略
変数を定義すると、コンストラクタで初期化値を代入する必要がありますが、数が多くなると大変です。
そこでTypescriptでは簡略化して表記する方法があります。
class Department {
// 変数名を定義する必要はaない
constructor(public name: string, private employees: string[]) {
// 何も記述しなく良い
}
}
上記の記述だけで、this.name
と this.employees
にアクセスできるようになります。
readonly
readonly
はその呼び方の通り readonly
をつけた変数が、初期化時以外、読み取り専用となり、再代入できなくなります。
class Department {
constructor(private readonly id: string, private name: string) {
// readonlyをつけると初期化時以外、再代入ができなくなる
}
}
継承
継承は、オブジェクト指向プログラミングの概念の一つであり、あるクラスが別のクラスのプロパティやメソッドを受け継ぐことを指します。これによって、既存のクラスを拡張して新しいクラスを定義することができます。
別で定義したクラスを継承することで、継承元の変数やメソッドを使うことができます。
extends
キーワードを使うことで継承ができます。クラスは1つのクラスしか継承できません。
class ITdepartment extends Department {
constructor(id: string, public admin: string[]) {
super(id, 'IT'); // superはthisキーワードの前に呼ばないといけない
}
addEmployee(employee: string): void {
if (employee === 'Max') {
return ;
}
this.employees.push(employee);
}
}
const IT = new ITdepartment('d2', ['Jhon', 'Max']);
IT.addEmployee('Max'); // 追加されない
IT.addEmployee('Sarah');
console.log(IT);
super
super
は親のクラスの constructor
を呼び出します。
constructor
で this
を呼ぶ前に必ず呼び出す必要があります。
override(オーバーライド)
同じメソッド名でメソッドを定義することで、元のメソッドを上書きできます。
上記の例では、6行目で addEmployee
メソッドを定義しており、Department
クラスにある addEmployee
メソッドを上書きしています。
protected
protected
キーワードは、private
と同じように変数の前につけ、そのクラスと継承先のクラスでのみ変数を参照できるようにします。
class Department {
protected employees: string[] = []; // protectedにすると継承先でも変数が使えるよーの話
constructor(private readonly id: string, private name: string) {
}
}
getter
getter
はプロパティの値を取得する際に使用されるメソッドです。通常のプロパティアクセスとは異なり、ゲッターメソッドを介してプロパティの値を取得することができます。これを使用することで、プロパティの値に対して追加の処理を行うことができます。
setter
setter
は、プロパティの値を設定する際に使用されるメソッドです。通常のプロパティ代入とは異なり、セッターメソッドを介してプロパティの値を設定することができます。これを使用することで、プロパティの値に対してバリデーションや変換などの処理を行うことができます。
class AccountingDepartment extends Department {
private lastReport: string;
constructor(id: string, private reports: string[]) {
super(id, 'Accounting');
this.lastReport = reports[0];
}
// getterの定義
get mostRecentReport() {
if (this.lastReport) {
return this.lastReport;
}
throw new Error('No report found.');
}
// setterの定義
set mostRecentReport(value) {
if (!value) {
throw new Error('Please pass in valid value.');
}
this.addReport(value);
}
addReport(text: string) {
this.reports.push(text);
this.lastReport = text;
}
printReports() {
console.log(this.reports);
}
}
const accountingDepartment = new AccountingDepartment('d3', []);
accountingDepartment.mostRecentReport = 'Year End Report';
console.log(accountingDepartment.mostRecentReport);
accountingDepartment.addReport('8/12 Something was wrong...');
accountingDepartment.addReport('8/13 Good luck.');
accountingDepartment.printReports();
static
static
は、変数やメソッドの前につけ、クラスをインスタンス化せずに使いたい場合に使用します。
class Department {
protected employees: string[] = [];
static ficicalYear = 2020; // static変数
constructor(private readonly id: string, private name: string) {
console.log(Department.ficicalYear); // クラス内部でstatic変数を呼び出す場合、thisを使わない
}
// staticメソッド
static createEmployee(name: string) {
return { name: name };
}
}
const employee1 = Department.createEmployee('John');
console.log(employee1, Department.ficicalYear); // {name: 'John'} 2020 が出力される
注意点としては、静的変数や静的メソッドは基本的に内部から this
で呼び出すことはできません。
内部で呼び出す場合は、クラス名そのものを記述する必要があります。
abstract(抽象)
abstract
は変数やメソッドの前につけて、継承先でその変数やメソッドをオーバーライドすることを強制するものです。
abstract
を使う場合は、クラス名の前にも abstract
をつける必要があり、abstract
をつけたクラスは抽象クラスとして扱われます。
// クラスの前にabsractをつける
abstract class Department {
constructor(protected readonly id: string, private name: string) {
}
// メソッド名の前にabstractをつける
// 抽象メソッドでは具体的な実装をしない
abstract describe(this: Department): void;
}
// 継承先のクラス
class ITdepartment extends Department {
constructor(id: string, public admin: string[]) {
super(id, 'IT');
}
// describeメソッドを必ず定義する必要がある
// 継承先のクラスで具体的な実装をする
describe(): void {
console.log(`IT Department - ID: ${this.id}`);
}
}
private constructorとSingleton(シングルトン)
シングルトンパターンとは、デザインパターンの1つで常に一意のインスタンスしか返却しないようにする手法です。
Typescirptでは、コンストラクタに private
をつけ、静的メソッドを呼び出すことで実現できます。
class AccountingDepartment extends Department {
// 内部からしかアクセスできない静的変数を定義
private static instance: AccountingDepartment;
// コンストラクタにprivateをつけて内部からしか呼び出せないようにするu
private constructor(id: string, private reports: string[]) {
super(id, 'Accounting');
}
// 静的メソッドを定義、中でインスタンス化を行う
static getInstance() {
if (!AccountingDepartment.instance) {
this.instance = new AccountingDepartment('d3', []);
}
return this.instance;
}
describe(): void {
console.log(`Accounting Department - ID: ${this.id}`);
}
// newではなくgetInstance()でインスタンスを作成する
const accountingDepartment = AccountingDepartment.getInstance();
以上です。