1. 背景
業務では最近抽象クラスを使って設計しています(TypeScript)。
先日他の方がインターフェースを使って同様のことをされていたので、両者の違い、ユースケースを整理したいと思ったからです。
2. 抽象クラスとは
直接インスタンスを作れないクラス
// Abstract class
abstract class Vehicle {
make: string;
model: string;
year: number;
constructor(make: string, model: string, year: number) {
this.make = make;
this.model = model;
this.year = year;
}
abstract start(): void;
abstract stop(): void;
}
// Concrete class extending Vehicle
class Car extends Vehicle {
numDoors: number;
constructor(make: string, model: string, year: number, numDoors: number) {
super(make, model, year);
this.numDoors = numDoors;
}
start(): void {
console.log("Car started.");
}
stop(): void {
console.log("Car stopped.");
}
}
// Concrete class extending Vehicle
class Motorcycle extends Vehicle {
numWheels: number;
constructor(make: string, model: string, year: number, numWheels: number) {
super(make, model, year);
this.numWheels = numWheels;
}
start(): void {
console.log("Motorcycle started.");
}
stop(): void {
console.log("Motorcycle stopped.");
}
}
// Example usage
const myCar = new Car("Toyota", "Camry", 2022, 4);
myCar.start();
myCar.stop();
const myMotorcycle = new Motorcycle("Harley-Davidson", "Sportster", 2021, 2);
myMotorcycle.start();
myMotorcycle.stop();
メリット
- 具象プロパティを抽象クラスで宣言できるので子クラスで実装しないで良い
デメリット
- 抽象クラスの具象なプロパティを呼び出す際に何を記述してるか把握する必要がある
- 親の具象メソッドで子クラスを考慮した条件分岐のメソッドを実装してしまうこともある(抽象クラスに関わらずですが)
3. インターフェースとは
クラスが実装すべきフィールドやメソッドを定義した型
// Interface
interface Vehicle {
make: string;
model: string;
year: number;
start(): void;
stop(): void;
}
// Concrete class implementing Vehicle interface
class Car implements Vehicle {
make: string;
model: string;
year: number;
numDoors: number;
constructor(make: string, model: string, year: number, numDoors: number) {
this.make = make;
this.model = model;
this.year = year;
this.numDoors = numDoors;
}
start(): void {
console.log("Car started.");
}
stop(): void {
console.log("Car stopped.");
}
}
// Concrete class implementing Vehicle interface
class Motorcycle implements Vehicle {
make: string;
model: string;
year: number;
numWheels: number;
constructor(make: string, model: string, year: number, numWheels: number) {
this.make = make;
this.model = model;
this.year = year;
this.numWheels = numWheels;
}
start(): void {
console.log("Motorcycle started.");
}
stop(): void {
console.log("Motorcycle stopped.");
}
}
// Example usage
const myCar: Vehicle = new Car("Toyota", "Camry", 2022, 4);
myCar.start();
myCar.stop();
const myMotorcycle: Vehicle = new Motorcycle("Harley-Davidson", "Sportster", 2021, 2);
myMotorcycle.start();
myMotorcycle.stop();
メリット
- 子クラスでプロパティを全て宣言するので処理を追いやすい、親クラスのプロパティを意図せず呼び出すことが起きない
デメリット
インターフェースでは型しか宣言できないので、子クラスでプロパティを全て宣言する必要がある
4. 最後に
以下の結論になりました。
子クラスで全てのプロパティを実装させることを優先する
それを実現させる為にはそれぞれで以下を意識します。
インターフェイスを使う場合
interfaceをどこかのタイミングで上書きされないように注意する
抽象クラスを使う場合
全てのプロパティにabstract修飾子をつける