🔖

FF5で例える継承・カプセル化・ポリモーフィズム

4 min read

継承

第二世界でガラフはエクスデスとの死闘の末、亡くなってします。
ガラフはクリスタルの力を通じて、孫のクルルに自分の全ての力を託したのでした。
これが継承です。

クルル(継承先)は、ガラフ(継承元)の全てを扱うことがでます。
加えて、クルル(継承先)は、ガラフ(継承元)の情報を上書きすることができます。

解説

Galufクラスにはそれまでのガラフの経験と動作が定義されています。
KluruクラスはGalufクラスを継承する事により、ガラフの経験と動作を受け継ぐことができます。
ですが、名前は違うのでクルルという名前に上書きしています。
また、Kluruクラスは見えていないGalufクラスの情報も持っています。
クルルは冒険を続け、レベルを上げていきマスターするジョブと使えるアビリティが増えていきます。

Galuf.ts
class Galuf {
    public name: string = "ガラフ";
    public level: number = 20;   
    public masterJobs: String[] = {
        "白魔道士",
        "時魔導士",
    };
    public abilities: String[] = {
        "白魔法",
        "時空魔法",
        "ちけい",
        "薬の知識",
        "調合",
    };
     
    public attack(): void {
        console.log(`${name}の攻撃`);
    }
}
 
default export Galuf;
Kliru.ts
import Galuf from "./Galuf";
 
class Kluru extends Galuf {
    name: string = "クルル";
}
 
default export Kluru;

カプセル化

風のクリスタルが壊れた後、風が吹かなくなり船で移動ができなくなってしまいました。
ですが、ファリス率いる海賊たちの船は動いています。
それは、シルドラが動力源となり船を引っ張っていたからでした。
船が必要だったバッツたちはファリスの船を盗もうとしました。
ですが、何で動いているかわからず動かす事ができずに海賊に捕まってしまいました。
これがカプセル化です。

解説

船の動力であるシルドラを隠蔽(カプセル化)しています。
直接シルドラをみることはできませんが、動力は何か聞けば教えてもらえます。
また、普通の船を引っ張らせているので、シルドラがいなければ船を動かすことはできません。
船を動かせるかどうか(シルドラがいるかどうか)は、動かしてみるまで隠した状態としています。

Ship.ts
class Ship {
    private const power: string = "シルドラ";
     
    public move(): void {
        if (this.canMove()) {
            console.log("船が進んでいます");
            return;
        }
        console.log(`${power}がないと動きません`);
    }
    
    public getPower(): string {
        return this.power;
    }
     
    private checkPower(): boolean {
        // シルドラいないよ
        return false;
    }
}

ポリモーフィズム

FF5の世界には船が4種類あります。
帆船・火力船・飛空挺・シルドラです。
乗っている船がどの船かわからなくても舵を握ればちゃんと動かす事ができます。
これをポリモーフィズムと言います。

解説

帆船も火力船も船である事が保証されています。(interface を継承する事によって保証される)
なので、舵はどの船を動かすのかということは気に掛けずに安全に動かす事ができます。

Rudder.ts
import ShipInterface from "./ShipInterface";
import SailingShip from "./SailingShip";
import FirepowerShip from "./FirepowerShip";
 
class Rudder {
    // イケてないけど別の話なので割愛
    public move(shipType: string) {
        const ship: ShipInterface = shipType === "帆船" ? new SailingShip() : new FirepowerShip();
        ship.move();
    }
}
ShipInterface.ts
interface ShipInterface {
    private const power: string;
    
    public move(): void;
    public getPower(): string;
    private checkPower(): boolean;
}
 
export default Ship;
SailingShip.ts
import ShipInterface from "./ShipInterface";
 
class SailingShip implements ShipInterface {
    private const power: string = "風";
    
    public move(): void {
        if (this.canMove()) {
            console.log("船が進んでいます");
            return;
        }
        console.log(`${power}がないと動きません`);
    }
     
    public getPower(): string {
        return this.power;
    }
     
    private checkPower(): boolean {
        // 風は吹いている
        return true;
    }
}
FirepowerShip.ts
import ShipInterface from "./ShipInterface";
 
class FirepowerShip implements ShipInterface {
    private const power: string = "火のクリスタル";
    
    public move(): void {
        if (this.canMove()) {
            console.log("船が進んでいます");
            return;
        }
        console.log(`${power}がないと動きません`);
    }
     
    public getPower(): string {
        return this.power;
    }
     
    private checkPower(): boolean {
        // 火のクリスタルは砕け散りました
        return false;
    }
}

上記では、interface を利用していますが、 move() の様に普遍となる場合は、抽象化クラスを利用することもできます。

Ship.ts
abstract class Ship {
    abstract private const power: string;
       
    public move(): void {
        if (this.canMove()) {
            console.log("船が進んでいます");
            return;
        }
        console.log(`${power}がないと動きません`);
    }
     
    public getPower(): string {
        return this.power;
    }
     
    abstract private checkPower(): boolean;
}
 
export default Ship;
SailingShip.ts
import Ship from "./Ship";
 
class SailingShip extends Ship {
    private const power: string = "風";
    
    private checkPower(): boolean {
        // 風のクリスタルは砕け散った
        return false;
    }
}