オブジェクト指向プログラミング(OOP)は、データとそのデータを操作するコードを含むことができるオブジェクトの概念に基づいたプログラミングパラダイムです。
TypeScriptでは、クラスはオブジェクト(特定のデータ構造)を作成し、状態の初期値(メンバ変数)を提供し、振る舞い(メソッド)の実装を行うための設計図として利用されます。
クラスの宣言は、以下のコードのように、classキーワード、クラス名、{}の順で使用することで作成することができます。
class Point {
}
Pointという名前の新しいクラスが作成されます。次にnewキーワードに続いてクラス名、そして空の引数を使用すると、Pointクラスの新しいインスタンスを作成することができます。
class Point {
}
let p1 = new Point();
クラス自体は設計図と考えることができ、インスタンスはこの設計図から作成されたオブジェクトです。
コンストラクタ関数はクラスのインスタンスが作成されるときに自動的に呼び出される特別なメソッドです。インスタンスを作成し、コンストラクタ関数を呼び出してみましょう。
class Point {
constructor(x: number) {
console.log(`Constructor called with x=${x}`);
}
}
let instance1 = new Point(100);
TypeScriptでは、クラスのボディで最初にプロパティを宣言し、個々のインスタンスで内部データを保存することができます。例えば、Pointクラスにxプロパティとyプロパティを追加してみましょう。
class Point {
x: number;
y: number;
constructor(v1: number, v2: number) {
this.x = v1;
this.y = v2;
}
}
let p1 = new Point(0, 10);
let p2 = new Point(10, 20);
console.log(p1.x); // 0
console.log(p2.y); // 20
この例では、コンストラクタ関数は、インスタンス作成時に引数を受け取り、その値をプロパティへ設定しています。プロパティ名x,yはnumber型で宣言されています。
では、具体例を用いて、クラスの実装を行っていきます。
TypeScriptを使って、二次元平面上の点をクラスを使って表してみましょう。任意の点は(x,y)によって表すことができます。
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
add(point: Point) {
return new Point(this.x + point.x, this.y + point.y);
}
}
let p1 = new Point(0, 10);
let p2 = new Point(10, 20);
let p3 = p1.add(p2);
console.log(p3); // {x: 10, y: 30}
上記の例では、Pointクラスは2つのメンバ変数xとyを持ち、xとyの値を受け取ってメンバ変数に代入するコンストラクタ関数を備えています。
また、このクラスには、別の点オブジェクトを取り込み、xとyの値が追加された新しい点オブジェクトを返すadd()というメソッドもあります。
オブジェクト指向プログラミングでは、インスタンスはクラスから生成されるオブジェクトです。インスタンスは、クラスのメンバ変数とそれを操作するためのメソッドのセットを持ちます。
例えば、上で紹介したPointでは、p1、p2はPointクラスのインスタンスになります。それぞれのインスタンスは、属性xとyにそれぞれ固有の値を持ち、その値を操作するためにクラスのメソッドを使用することができます。
TypeScriptでクラスのインスタンスを生成するには、newキーワードに続いてクラス名を指定し、さらにコンストラクタ関数に必要なパラメータを指定します。次のコードは、Pointクラスのインスタンスを生成を示しています。
let p1 = new Point(0, 10);
この例では、p1がPointクラスのインスタンスで、xが0、yが10に設定されています。
コンストラクタ関数はクラスのインスタンスが作成されるときに自動的に呼び出される特別なメソッドです。コンストラクタは、オブジェクトの状態を初期化し、必要な設定を行うために使用されます。
コンストラクタは、constructorキーワードとそれに続く引数のセットで定義されます。
class Car {
make: string;
model: string;
year: number;
isRunning: boolean;
currentSpeed: number;
constructor(make: string, model: string, year: number) {
this.make = make;
this.model = model;
this.year = year;
this.isRunning = false;
this.currentSpeed = 0;
}
start() {
this.isRunning = true;
console.log("Car is started");
}
stop() {
this.isRunning = false;
this.currentSpeed = 0;
console.log("Car is stopped");
}
accelerate(speed: number) {
if (!this.isRunning) {
console.log("Cannot accelerate, car is not running");
return;
}
this.currentSpeed += speed;
console.log(`Accelerating to ${this.currentSpeed} mph`);
}
brake() {
if (!this.isRunning) {
console.log("Cannot brake, car is not running");
return;
}
this.currentSpeed = 0;
console.log("Car has stopped");
}
}
let car1 = new Car("Tesla", "Model S", 2020);
console.log(car1.isRunning); // false
console.log(car1.currentSpeed); // 0
car1.start(); // Car is started
car1.accelerate(50); // Accelerating to 50 mph
car1.accelerate(40); // Accelerating to 90 mph
car1.brake(); // Car has stopped
car1.stop(); // Car is stopped
Carクラスのインスタンスが作成されると、自動的にコンストラクタが呼び出され、引数として渡された値がオブジェクトの状態を初期化するために使用されます。
thisキーワードはクラスの現在のインスタンスを指します。このキーワードは、現在のオブジェクトのメンバ変数やメソッドにアクセスするために使用されます。
class BankAccount {
balance: number;
constructor(initialBalance: number) {
this.balance = initialBalance;
}
deposit(amount: number) {
this.balance += amount;
console.log(`Deposited ${amount}, new balance is ${this.balance}`);
}
withdraw(amount: number) {
if (amount > this.balance) {
console.log("Insufficient funds");
} else {
this.balance -= amount;
console.log(`Withdrew ${amount}, new balance is ${this.balance}`);
}
}
}
let account = new BankAccount(1000);
account.deposit(500); // Deposited 500, new balance is 1500
account.withdraw(200); // Withdrew 200, new balance is 1300
account.withdraw(2000); // Insufficient funds
この例のthisキーワードは、現在のオブジェクトのメンバ変数balanceにアクセスするために使われます。またコンストラクタ関数内でも使用され、現在のオブジェクトのメンバ変数を初期化します。この例では、this.balance = initialBalanceは、コンストラクタの引数initialBalanceの値をそれぞれメンバ変数balanceに代入しています。
注意すべきは、このキーワードはクラス内でのみ使用可能で、クラス外ではエラーになることです。また、静的メンバはクラスのインスタンスに属さないため、thisキーワードを静的メソッドや変数で使用することはできません。
オブジェクト指向プログラミングでは、クラス変数(静的変数とも呼ばれる)は、クラスの特定のインスタンスに属するのではなく、クラス全体に属する変数です。staticというキーワードで定義します。
例えば、TypeScriptでは、以下のようにπというクラス変数を定義し、値を3.14とします。
class Circle {
static pi = 3.14;
radius: number;
constructor(radius: number) {
this.radius = radius;
}
static calculateArea(radius: number) {
return Circle.pi * radius * radius;
}
}
この例では、変数piはCircleクラスの静的変数として定義され、その値は3.14に設定されています。この変数は、クラスのインスタンスから、あるいはクラス名を使ってクラス自身からアクセスすることができます。
クラス変数にアクセスするには、クラス名の後にドット演算子、そして変数名を指定します。たとえば、上記の例でπの値にアクセスするには、次のようなコードを使用することができます。
Circle.pi
注意点としては、クラス変数はクラスの全インスタンスで共有され、クラス変数に加えられた変更は全インスタンスに反映されることです。
Note: オブジェクトについては、CS基礎/中級/オブジェクトで詳しく学習できます。
オブジェクト指向プログラミングでは、クラスの変数やメソッドへのアクセスを制御するために、public, protected, privateのようなアクセス修飾子と呼ばれるキーワードが使われます。
publicキーワードを使うと、メンバはクラスの内外を問わず、どこからでもアクセスできます。
class MyClass {
public member1: string;
public method1() {}
}
protectedキーワードを使うと、メンバはそのクラスおよび派生クラスの中でのみアクセスすることができます。クラスや派生クラスの外からアクセスすることはできません。
class MyClass {
protected member2: string;
protected method2() {}
}
privateキーワードを使うと、メンバはクラス内でのみアクセス可能で、クラスやその派生クラスの外部からアクセスすることはできません。
class MyClass {
private member3: string;
private method3() {}
}
デフォルトでは、TypeScriptはすべてのメンバをpublicとみなすので、明示的にpublicキーワードを使用する必要はありません。
class BankAccount {
private balance: number;
public accountNumber: number;
constructor(initialBalance: number, accountNumber: number) {
this.balance = initialBalance;
this.accountNumber = accountNumber;
}
public deposit(amount: number) {
this.balance += amount;
console.log(`Deposited ${amount}, new balance is ${this.balance}`);
}
public withdraw(amount: number) {
if (amount > this.balance) {
console.log("Insufficient funds");
} else {
this.balance -= amount;
console.log(`Withdrew ${amount}, new balance is ${this.balance}`);
}
}
protected getBalance(): number {
return this.balance;
}
private logTransaction(transactionType: string, amount: number) {
console.log(`${transactionType} of ${amount} is completed, your current balance is ${this.balance}`);
}
}
let account = new BankAccount(1000, 123456);
console.log(`Account number: ${account.accountNumber}`) // Account number: 123456
account.deposit(500); // Deposited 500, new balance is 1500
account.withdraw(200); // Withdrew 200, new balance is 1300
console.log(account.getBalance()); // エラー。protectedなのでアクセスすることはできない
console.log(account.balance); // エラー。privateなのでアクセスすることはできない
この例では、BankAccountクラスは2つのメンバ変数balanceとaccountNumberを持っていて、それぞれ異なるアクセスレベルを持っています。balanceはprivateに設定されているので、クラスの外からアクセスすることはできませんが、accountNumberはpublicに設定されているので、どこからでもアクセスすることができます。
また、このクラスはdeposit(), withdraw(), getBalance(), logTransaction()の4つのメソッドを持っています。deposit()と withdraw()はpublicで、どこからでもアクセスすることができ、getBalance()はprotectedで、クラス内と派生クラスからアクセスすることができ、logTransaction()はprivateで、クラス内のみアクセスすることができます。
注意点としては、クラスの外部からgetBalance()メソッドを呼び出すとprotectedメソッドなのでエラーを起こします。logTransaction()メソッドを呼び出してもprivateメソッドなのでエラーになります。このようにして、クラスの内部状態を保護し、公開したいデータにアクセスする方法を提供することができます。
Note: オブジェクト指向プログラミングについては、CS基礎/プログラミングパラダイム/OOPで詳しく学習できます。