2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

駆け出しpythonエンジニアのまとめ デザインパターンまとめ5(singleton)

Last updated at Posted at 2021-05-10

趣旨

駆け出しエンジニアの備忘録の垂れ流し。
 ※ 出典漏れがあればご指摘ください

出典:
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

実装例2
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つしか生成されないことを保証する。
    そんな状況を実装する場合に使う
2
2
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?