🐈

一日一処: 「吾輩は猫である名前はまだない」とちゃんと言えるオブジェクト指向の言語たち(TypeScriptを用いて)

2024/02/26に公開

オブジェクト指向

多くのオブジェクト指向の言語では、クラスの定義が行なえ、この後紹介する方法も基本的に付随している。今回は、TypeScriptを用いて、タイトルに有る「吾輩は猫である名前はまだない」を表現しつつ、仕組みについて記述する。

吾輩は猫である

まずは、その対象が猫であるということを決定づけるわけだが、プログラムの世界では、猫を生成することも容易い。

class Animal {}

class Cat extends Animal {}

const cat: Animal = new Cat()

ひとまず、動物クラスと猫クラスを定義し、一つの猫を生成した。
ついでなので、ネズミも増やしておこう。

class Animal {}

class Cat extends Animal {}
class Mouse extends Animal {}

const cat: Animal = new Cat()
const mouse: Animal = new Mouse()

現在、生成された猫とネズミは、それぞれ動物クラスの定義の元、変数に格納している。そのため、これ以降の処理では、cat変数もmouse変数も中身に動物クラスということ以外は、何が入っているかがわからない状態となる。
聞くところによると、この世界線では、動物が自身の種類を自認しているそうなので、抽象化した動物クラスには、共通して、種類(クラス名)を返却するゲッターメソッドを追加した。

abstract class Animal {
    get type(): string { return this.constructor.name }
}
// 省略
console.log(cat.type)   // Cat
console.log(mouse.type) // Mouse

名前はまだない

名前も理解している動物というのも非常に稀だが、そういう世界線だから仕方ない。
ただ、ここには一つ問題がある。「まだない」ということは、これまで存在しているものではあるが現時点では名前が無いということと、これから作られようとしているものにまだ名前がついていないという2つの解釈がある。とはいっても、これを話しているのは猫そのものなので、「名前がまだない」と話しているのは本人だろう。よって、前者の存在しているもので、現時点で名前が無い状態といえる。
よって、動物クラスの初期状態として、名前の属性を与えることとする。

abstract class Animal {
    #name: string = ''
    get name(): string { return this.#name }
    get type(): string { return this.constructor.name }
}
// 省略
console.log(cat.name)   // empty
console.log(mouse.name) // empty

合わせて、ゲッターを定義して、プライベートである名前の属性を取得できるようにした。これで、名付けられていない動物には、名前がまだない(名前が空文字)という状況となった。

喋らせる

最後に、動物として、自認している種類と、名前の有無を教えてくれるようにメソッドを追加した。

abstract class Animal {
    #name: string = ''
    get name(): string { return this.#name }
    get type(): string { return this.constructor.name }
    #say(msg: string): void { console.log(msg) }
    saySelfType(): void {
        this.#say(`I Am a ${this.type}.`)
    }
    saySelfName(): void {
        this.#say(`${
            this.#name === ''
            ? 'As yet I have no name.'
            : 'As I have name.'
        }`)
    }
    saySelf(): void {
        this.saySelfType()
        this.saySelfName()
    }
}

書きながら、何を分けのわからないことをしているのかと、考えたが、良い出来だろう。生成した猫に使ってみるとこうだ。

const cat: Animal = new Cat()
cat.saySelf()
// I Am a Cat.
// As yet I have no name.

原書通りだ。素晴らしい。世界観的に、教師も追加しなければならないかもしれないが、今回はやめておこう。
そして、この猫は、生意気なことに、名前の有無を教えてくれているだけだ。すなわち、名前がつけられていたとしても、名前そのものを教えてくれるわけではない、ということは、実現しなけてはならない。名前のセッターを追加する。

abstract class Animal {
    #name: string = ''
    set name(name: string) { this.#name = name }
    get name(): string { return this.#name }
    get type(): string { return this.constructor.name }
    #say(msg: string): void { console.log(msg) }
    saySelfType(): void {
        this.#say(`I Am a ${this.type}.`)
    }
    saySelfName(): void {
        this.#say(`${
            this.#name === ''
            ? 'As yet I have no name.'
            : 'As I have name.'
        }`)
    }
    saySelf(): void {
        this.saySelfType()
        this.saySelfName()
    }
}

class Cat extends Animal {}

const cat: Animal = new Cat()
cat.name = 'Doraemon'
cat.saySelf()
// I Am a Cat.
// As I have name.

ちなみに、動物クラスに機能を追加していたため、ネズミクラスでも同様の事が可能だ。もしかすると、ネズミが教師に憧れる世界線だったかもしれない。

const mouse: Animal = new Mouse()
mouse.saySelf()
// I Am a Mouse.
// As yet I have no name.

mouse.name = 'Mickey'
mouse.saySelf()
// I Am a Mouse.
// "As I have name.

たとえ、クラブのリーダーだったとしても、名前は明かすことができない。

Discussion