背景
「良いコード悪いコードで学ぶ設計入門」の復習の為です。TypeScriptで記述します。
今回は「条件文の重複をinterfaceで解消する」ことについてです。
車の基本仕様
項目 | 説明 |
---|---|
名前 | 車の名前 |
スピード | 車の速さ |
燃料の量 | 消費する燃料の量 |
車一覧
項目 | 説明 |
---|---|
芝刈り機 | 速さは遅く、消費燃料の量は大きい |
レースカー | 速さは速く、消費燃料の量は大きい |
コンパクトカー | 速さは普通で、消費燃料の量は小さい |
悪いコード(同じ条件式のswich文が複数書かれていく)
type CarType = "Lawnmower" | "RaceCar" | "CompactCar";
class CarManager {
getName(carType: CarType) {
let name: string = "";
switch (carType) {
case "Lawnmower":
name = "Lawnmower";
case "RaceCar":
name = "RaceCar";
case "CompactCar":
name = "CompactCar";
}
return name;
}
getSpeed(carType: CarType) {
let speed: number = 0;
switch (carType) {
case "Lawnmower":
speed = 10;
case "RaceCar":
speed = 100;
case "CompactCar":
speed = 50;
}
return speed;
}
getFuelAmount(carType: CarType) {
let fuelAmount: number = 0;
switch (carType) {
case "Lawnmower":
fuelAmount = 10;
case "RaceCar":
fuelAmount = 10;
case "CompactCar":
fuelAmount = 2;
}
return fuelAmount;
}
}
これだと以下の問題が生じます。
- 新たな車が追加された場合(例:SUV)、どこかのメソッドのcase文に追加漏れする恐れがあります。
- 他のメソッドを追加する際(例:車検の有効期限)に同様のswitch文を記述する必要があります。
↓
修正漏れと可読性の低下による開発生産性低下に繋がります。
良いコード(swich文を一つにまとめる)
class Car {
name = "";
speed = 0;
fuelAmount = 0;
Car(carType: string) {
switch (carType) {
case "Lawnmower":
this.name = "Lawnmower";
this.speed = 10;
this.fuelAmount = 10;
case "RaceCar":
this.name = "RaceCar";
this.speed = 100;
this.fuelAmount = 10;
case "CompactCar":
this.name = "CompactCar";
this.speed = 50;
this.fuelAmount = 2;
}
}
}
1箇所に記述されているので仕様変更の際に修正漏れを起こしづらくなります。
しかし、caseが増えていくとクラスの行数が膨大になり、こちらも修正漏れと可読性の低下による開発生産性低下に繋がります。
そこで次の修正をします。
良いコード(interfaceを使う)
interface Car {
getName(): string;
getFuelAmount(): number;
getSpeed(): number;
}
class Lawnmower implements Car {
getName() {
return 'Lawnmower';
}
getFuelAmount() {
return 10;
}
getSpeed() {
return 10;
}
}
class RaceCar implements Car {
getName() {
return 'RaceCar';
}
getFuelAmount() {
return 10;
}
getSpeed() {
return 100;
}
}
class CompactCar implements Car {
getName() {
return 'CompactCar';
}
getFuelAmount() {
return 2;
}
getSpeed() {
return 50;
}
}
let car = new Lawnmower();
car.getName(); // "Lawnmower"
car = new RaceCar();
car.getName(); // "RaceCar"
共通のCar interfaceを継承することで型の判定が必要なく各子クラスのオーバーライドした処理を実行してくれます。
switch文の代わりにオブジェクトを使用します。
const carMap: { [key in CarType]: Car } = {
Lawnmower: new Lawnmower(),
RaceCar: new RaceCar(),
CompactCar: new CompactCar(),
};
const carInstance = carMap['Lawnmower'];
console.log(car.getName());
// 'Lawnmower'
console.log(car.getFuelAmount());
// 10
console.log(car.getSpeed());
// 10```
以上の実装により、条件分岐なしで車の種類に応じて処理を切り替えられています。
オブジェクトのkeyによってそれに応じてinstanceを返します。
interfaceによって処理を切り替える設計をStrategyパターンといいます。
Strategyパターンでクラスによって処理を切り替えられます。
また、以下の利点もあります。
未実装のメソッドを検知し、修正漏れを予防
getSpeedを実装し忘れたという設定です。
class SUV implements Car {
getName() {
return 'SUV';
}
getFuelAmount() {
return 2;
}
}
以下のようにエディタに実装もれをしていることが表示されています。
最後に
状況に応じて良い設計をできるようにしていきたいです。