『独習デザインパターン』をPythonに読み替える Singletonパターン

## Java から Python へ翻訳しよう
『独習〜』は Java で書いてあります。
Java は Java なのでそのまま Python にコードを書き換えるのは難しい部分が私にはあります。
書き換えるのが難しいと思ったところで、『あの言語でできることがこの言語ではできない』
ということは、難易度の差こそあれそんな場面はあんまりないと聞きます。
聞く機会がなかっただけかもしれんけど。

[「デザインパターンってなんぞ？」](https://qiita.com/GuitarBuilderClass/items/3d4ba021879c78cae48c)というのを飲み込んでいくため、
どうも簡潔すぎる内容の『実践 Python3』を読む前の準備をしとうございます。
今回は Singleton パターンを翻訳していきます。

## 要件:

- 本システムではフットサル場の経営をサポートする
- 本システムではフットサルコートは一面しかないことを想定する
- 本システムではダブルブッキングを避けることができる

### シングルトンパターンを適用しない場合

```useless_singleton.py
#! /usr/bin/env python3

HOURS: int = 24


class CourtScheduler:
    """予約管理クラス"""

    def __init__(self) -> None:
        self.__initialize()

    def __initialize(self) -> list:
        """
        予約台帳を初期化する
        Returns:
            list: HOURS分の空き枠がある予約台帳
        """
        self.booking: list = list()
        for _ in range(HOURS):
            self.booking += " "
        return self.booking

    def schedule(self, hour: int, person: str) -> None:
        """
        予約者が指定する時間に予約を入れる
        Args:
            hour (int): 予約希望時間
            person (str): 予約希望者
        """
        self.booking[hour]: object = person


class Receptionist:
    """予約受付クラス"""

    # 営業開始時間〜終了時間
    START: int = 9
    END: int = 20

    def __init__(self, name: str, scheduler: CourtScheduler) -> None:
        """
        予約受付係の設定をする
        Args:
            name (str): 予約受付係の名前
        """
        self.name: str = name
        self.court_scheduler: CourtScheduler = scheduler
        self.hours: int = HOURS

    def make_reservation(self, hour: int, person: str) -> None:
        """
        予約が受付できるか確認する
        Args:
            hour (int): 予約者の予約希望時間
            person (str): 予約希望者
        """
        if (hour <= 0) or (hour > self.hours - 1):
            message = "指定時間が間違っています"
        elif (self.END < hour) or (hour < self.START):
            message = "営業時間外です"
        else:
            if self.court_scheduler.booking[hour] == " ":
                self.court_scheduler.schedule(hour, person)
                message = f"{self.name}が{hour}時に{person}くんの予約を入れました"
            else:
                person2 = self.court_scheduler.booking[hour]
                message = f"{hour}時は既に{person2}くんの予約が入っております\n"
                message += f"あぁっと{person}くんふっ飛ばされた!!"
        print(message)


class Main:
    """メイン処理クラス"""
    court_scheduler: CourtScheduler = CourtScheduler()
    receptionistA: Receptionist = Receptionist("A", court_scheduler)
    receptionistB: Receptionist = Receptionist("B", court_scheduler)

    receptionistA.make_reservation(9, "日向")
    receptionistA.make_reservation(12, "マーガス")
    receptionistA.make_reservation(18, "新田")
    receptionistB.make_reservation(10, "大空")
    receptionistB.make_reservation(11, "岬")
    receptionistB.make_reservation(12, "森崎")

    print()
    goal_post: CourtScheduler = CourtScheduler()
    receptionistC = Receptionist("ゴールポスト", goal_post)
    receptionistC.make_reservation(12, "森崎")


if __name__ == '__main__':
    Main()

```

#### 実行結果

```
Aが9時に日向くんの予約を入れました
Aが12時にマーガスくんの予約を入れました
Aが18時に新田くんの予約を入れました
Bが10時に大空くんの予約を入れました
Bが11時に岬くんの予約を入れました
12時は既にマーガスくんの予約が入っております
あぁっと森崎くんふっ飛ばされた!!

ゴールポストが12時に森崎くんの予約を入れました

Process finished with exit code 0
```

~~森崎くん、ゴールポストのファインセーブでふっ飛ばされるのを回避してしまう。~~
新しいインスタンスを生成すると予約台帳がまっさらになってしまうんですね。
これでは~~森崎くんを吹っ飛ばせなくて~~本来の役割を果たすことができません。



### シングルトンパターンを適用した場合
Java は Java なので Pythonと異なり、`public`や`private`でアクセス制限ができるんですよね。
Java はコンストラクタを`private`にできるんか。知らんかった。

で、参考書を読みながら(コンストラクタを`private`にしろと！)と戸惑いました。
Python でインスタンスを初期化するときは `__init__` が実行されるのでは？
`____init__`にするの？(アンダーバーを２個増やした)
気分は「この利休にまっ茶ラテを作れと！」です。

あらやだ、このコード Java くさい……。と言われ石を投げられない方法がきっとあるはず。
そう考え我々は侍エンジニアを避け蛇が蠢く公式ドキュメントへと向かった。

```shingleton.py
#! /usr/bin/env python3

HOURS: int = 24


class CourtScheduler:
    """予約管理クラス"""
    _has_instance = None

    def __new__(cls):
        if not cls._has_instance:
            cls._has_instance = super(
                CourtScheduler, cls).__new__(cls)
            cls.__initialize()
        return cls._has_instance

# (2018/12/15 16:11追記)
# __new__の戻り値を__init__　に渡すとselfで呼び出せるよって
# Mypyを入れて遊んでいる途中で気づいたんだけど、
# だったらなんで前のコード動いていたのだろうか？
# cls.booking と self.booking が混在していても
# 同じインスタンスから呼び出されるなら同じものを参照しているのかな？

    def __init__(self) -> None:
        pass

    @classmethod
    def __initialize(cls) -> list:
        """
        予約台帳の初期化をする
        Returns:
           1ページも埋まっていない本
        """
        cls.booking = list()
        for __ in range(HOURS):
            cls.booking += " "
        return cls.booking

    @classmethod
    def schedule(cls, hour: int, person: str) -> None:
        """
        予約者が指定する時間に予約を入れる
        Args:
            hour (int): 予約希望時間
            person (str): 予約希望者
        """
        cls.booking[hour] = person


class Receptionist:
    """予約受付クラス"""

    # 営業開始時間〜終了時間
    START: int = 9
    END: int = 20

    def __init__(self, name: str) -> None:
        """
        予約受付係の設定をする
        Args:
            name (str): 予約受付係の名前
        """
        self.name: str = name

    def make_reservation(self, hour: int, person: str) -> None:
        """
        予約が受付できるか確認する
        Args:
            hour (int): 予約者の予約希望時間
            person (str): 予約希望者
        """
        court_scheduler = CourtScheduler()
        if (hour <= 0) or (hour > HOURS - 1):
            message = "指定時間が間違っています"
        elif (self.END < hour) or (hour < self.START):
            message = "営業時間外です"
        else:
            if court_scheduler.booking[hour] == " ":
                court_scheduler.schedule(hour, person)
                message = f"{self.name}が{hour}時に{person}くんの予約を入れました"
            else:
                person2 = court_scheduler.booking[hour]
                message = f"{hour}時は既に{person2}くんの予約が入っております\n"
                message += f"あぁっと{person}くんふっ飛ばされた!!"
        print(message)


class Main:
    """メイン処理クラス"""
    receptionistA: Receptionist = Receptionist("A")
    receptionistB: Receptionist = Receptionist("B")

    receptionistA.make_reservation(9, "日向")
    receptionistA.make_reservation(12, "マーガス")
    receptionistA.make_reservation(18, "新田")
    receptionistB.make_reservation(10, "大空")
    receptionistB.make_reservation(11, "岬")
    receptionistB.make_reservation(12, "森崎")

    print()
    receptionistC: Receptionist = Receptionist("ゴールポスト")
    receptionistC.make_reservation(12, "森崎")


if __name__ == '__main__':
    Main()

```

#### 実行結果

```
Aが9時に日向くんの予約を入れました
Aが12時にマーガスくんの予約を入れました
Aが18時に新田くんの予約を入れました
Bが10時に大空くんの予約を入れました
Bが11時に岬くんの予約を入れました
12時は既にマーガスくんの予約が入っております
あぁっと森崎くんふっ飛ばされた!!

12時は既にマーガスくんの予約が入っております
あぁっと森崎くんふっ飛ばされた!!

Process finished with exit code 0
```


何度でも森崎くんが吹っ飛ばされるのを見れて嬉しい。

「インスタンスを一度でも生成したか」のフラグを用意して
`super(クラス名, cls).__new__(cls, *args, **kwargs)`のあとに初期化の際に処理したいものを並べると
Singleton パターンとして機能するみたい。


## Python で Singleton パターンを実装するには
[`__new__`は新しいインスタンスが作成されるときに呼び出される](https://docs.python.jp/3/reference/datamodel.html#object.__new__)ようで、
ドキュメントには
> 典型的な実装では、クラスの新たなインスタンスを生成するときには super().__new__(cls[, ...]) に適切な引数を指定して
> スーパクラスの __new__() メソッドを呼び出し、新たに生成されたインスタンスに必要な変更を加えてから返します。

今まで`__init__`しか触ってこなかったけど、`__new__`というものがあって、それが先に処理をしていたのね。
`__new__`で作って`__init__`でこねくり回していたらしい。
今後もこういう使い方するんだろうか。覚えておこう。

### 実践的には
ちなみに『実践〜』では
「インスタンスなんぞ立てなくてもグローバルなモジュール作ってそこから値を出し入れしろ」(大訳)
と身も蓋もないこと書いていますね。
なるほどね。