趣旨
駆け出しエンジニアの備忘録の垂れ流し。
※ 出典漏れがあればご指摘ください
出典:
https://yamakatsusan.web.fc2.com/
https://dackdive.hateblo.jp/
https://www.oreilly.co.jp/books/9784873117393/
singletonパターン概要
生成に関するデザインパターンの1つ。
あるクラスのインスタンスが1つしか生成されないように制限する、あるいは1つしか生成されないことを保証する。
DB connectionなどに利用するケースが多い.
実装方針
- Singletonクラスをスーパクラスとして実装。インスタンスを取得するメソッド(get_instance)を実装。
- get_instanceの記載はほぼテンプレ化しているっぽいのでいずれかの方法で利用
- Singletonサブクラスでは実処理(やりたいこと)を記載。
補足
pythonではインスタンス生成の際、__new__
メソッドが呼ばれる。引数として、第一引数に生成するクラスと、以降に適切な引数を定義して、__init__
メソッドに渡される。
ただし、__new__
メソッドがインスタンスを返さない場合、__init__
メソッドは呼び出されない。
__init__
メソッドから非None値を返してはいけない、 __init__
は__new__
で生成されたインスタンスをカスタマイズするようなふるまいのため、非Noneが変えるとTypeErrorになる。
また、サブクラスが__init__
メソッドを持つ場合、インスタンスの基底クラス部分が適切に初期化されること保証する必要があるため、super().__init__([args...])
のような記載が必要なる、らしい。
super()
は 親クラスを参照する場合に利用する。第一引数にはクラス、第二引数にはインスタンスを指定することができる。
# https://yamakatsusan.web.fc2.com/pythonpattern05.htmlより引用
# 書き方1
class Singleton:
def __new__(cls, *args, **kwargs):
# args,kwargsとしておけば使い回せるのでこうしているが、明確に書くなら避けるべき
if not hasattr(cls, "__instance"):
cls.__instance = \
super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls.__instance
# 書き方2
class Singleton:
@classmethod
def get_instance(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
cls._instance = cls(*args, **kwargs)
return cls._instance
# 書き方3
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
# 書き方4
class Singleton(object):
_instances = dict()
def __new__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = object.__new__(cls, *args, **kwargs)
return cls._instances[cls]
# サブクラス
class DBConnection(Singleton):
def __init__(self):
self.connection = get_connection()
def get_connection_state(self):
hoge()
# client側
def main():
connection = DBConnection()
or
connection = DBConnection.get_instance()
signletonはアンチパターンらしい
問題点
- グローバル変数と同義のため
- 複数インスタンス化出来た方が良い事が分かった際の影響が絶大
- 依存関係を見えにくくし、コードが読みづらくなる。
- singletonクラスを利用するコードのユニットテストが難しくなる。(外部から渡せないオブジェクトはモックにする事が難しい。)
解決策
- 状態を初期化できるようにする⇛singletonクラスのテストを楽にする
- 依存性の注入 (Dependency Injection) の利用⇛singletonクラスを利用するコードのテストを楽にする
- 本当にあるクラスのインスタンスが1つしか生成されないように制限する、あるいは1つしか生成されないことを保証するのか、再度設計を見直す(Singletonを採用しない)
引用元:https://qiita.com/mo12ino/items/abf2e31e34278ebea42c
class Singleton:
def __new__(cls, *args, **kwargs):
# args,kwargsとしておけば使い回せるのでこうしているが、明確に書くなら避けるべき
if not hasattr(cls, "__instance"):
cls.__instance = \
super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls.__instance
# サブクラス
class DBConnection(Singleton):
def __init__(self):
self.connection = get_connection()
def refresh(self):
self.connection = None
def get_connection_state(self):
hoge()
# client側
def main(connection: DBConnection):
connection.get_connection_state()
# test1: main側のテスト
def test_main()
with patch('DBConnection', spec=DummyConnection) as dc:
assert main(dc)
# test2: DBconnectionクラスのメソッドのテスト
def test_db_connection1():
connect = DBConnection()
assert connect
def test_db_connection2():
connect = DBConnection()
connect.refresh()
assert not connect
メリットと使い所
- あるクラスのインスタンスが1つしか生成されないように制限する、あるいは1つしか生成されないことを保証する。
そんな状況を実装する場合に使う