🎉

Python100本ノック#11 「Singleton」デザインパターン

2023/12/03に公開

Singletonデザインパターンとは

OOP (bject-oriented programming) におけるデザインパターンの一つであり、アプリケーション全体であるクラスのインスタンスは絶対に一つしか存在しないことを保証するンパターンである。
Singleton を用いることでオブジェクトに利用されるメモリ領域といったリソースを節約できる。

Singletonを利用しない実装例

音楽プレイヤー機能を具体例にすると、

# 音楽プレイヤークラス
class MusicPlayer(object):
    # 初期化
    def __init__(self, name):
        self.name = name

    # 音楽プレイメソッドを定義
    def start(self):
        print(f'{self.name} を再生開始')

# 1、music1をインスタンス化
mp1 = MusicPlayer('Shake It Off')
mp1.start()

# 2、music2をインスタンス化
mp2 = MusicPlayer('純恋歌')
mp2.start()

output

Shake It Off を再生開始
純恋歌 を再生開始

メモリ領域

Songごとにメモリでそのインスタンスを作成する必要があるのため、メモリの使用効率が非常に悪い。

Singletonデザインパターンの必要性

上記のようにSingletonを利用することでMusicPlayer()クラスのインスタンスを1つしか存在しないようにする実行を行うことでメモリ領域の心配は不要になる。

__new__()

PythonでSingletonデザインパターンは__new__()を利用する実装になる。
インスタンス作成時に、オブジェクトのメモリ領域を確保するために最初に呼び出されるメソッドである。
__new__()はStaticメソッドであるため、呼び出す際に自身のクラスclsをパラメータとして渡す必要がある。
__new__()の主な用途:

  1. オブジェクトようのメモリを確保する
  2. オブジェクトの参照先を返す

__new__()具体例

# 音楽プレイヤークラス
class MusicPlayer(object):
    # __new()__をオーバーライド
    def __new__(cls, *args, **kwargs):
        print('1、メモリ確保')
        print('2、インスタンスのメモリアドレスを返却')
        return super().__new__(cls)

    def __init__(self, name):
        self.name = name

# 1、music1をインスタンス化
music1 = MusicPlayer('Shake It Off')
print(music1)

# 2、music2をインスタンス化
music2 = MusicPlayer('純恋歌')
print(music2)

output

5と6行目のコードは先に実行されることがわかる。

1、メモリ確保
2、インスタンスのメモリアドレスを返却
<__main__.MusicPlayer object at 0x00000224378E2650>
1、メモリ確保
2、インスタンスのメモリアドレスを返却
<__main__.MusicPlayer object at 0x0000022437845010>

Singletonを利用する実装例

# 音楽プレイヤークラス
class MusicPlayer(object):
    # メモリアドレスを記録するためのクラス属性を定義する
    instance_addr = None
    # __new()__をオーバーライド
    def __new__(cls, *args, **kwargs):
        print('1、メモリ確保')
        print('2、インスタンスのメモリアドレスを返却')
        # MusicPlayer()クラス方のインスタンスはメモリに存在するかを確認する。
        if cls.instance_addr is None:
	    # 存在しない場合は、新たにメモリを確保しそのアドレスをinstance_addrに格納。
            cls.instance_addr = super().__new__(cls)
	    # 存在する場合は、何もしない。
	# 既存インスタンスのメモリアドレスを返却する。
        return cls.instance_addr

    def __init__(self, name):
        self.name = name

# 1、music1をインスタンス化
music1 = MusicPlayer('Shake It Off')
print(music1)

# 2、music2をインスタンス化
music2 = MusicPlayer('純恋歌')
print(music2)

output

メモリアドレスは変わらないことがわかる。

1、メモリ確保
2、インスタンスのメモリアドレスを返却
<__main__.MusicPlayer object at 0x000002A1ED30BF90>
1、メモリ確保
2、インスタンスのメモリアドレスを返却
<__main__.MusicPlayer object at 0x000002A1ED30BF90>

コード解説

__new__()をオーバーライドしたことで、MusicPlayerクラス型のインスタンスを定義されたら、同じ型のインスタンスがメモリに存在するかを確認する。存在すればそのメモリ領域を利用する。こうすることでMusicPlayer()型のインスタンスは1つしかアプリに存在しないことを保証できる。

メモリ領域

Discussion