デザインパターン(Design Pattern)#Singleton


設計を意識したコードが書けるようになる為に、デザインパターン修行しました。
他のDesign Patternもちょくちょく出していきます。

前置き

デザインパターンをどういう時に、何を、どう使うのかを理解することが一先ずの目標。
(Javaというか静的型付言語は初めてで、且つpython歴もそんなに長くないので、Pythonistaぽっくないところがあると思います。ご指摘ございましたらご教授ください。)

まず、そもそもデザインパターンってどういうものかってとこから。

デザインパターンとは

ソフトウェア開発におけるデザインパターン(型紙(かたがみ)または設計パターン、英: design pattern)とは、過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものである。

要は、温故知新てことですね。
先人の使えるノウハウが使えるなら使いましょうってことね。

じゃあ、そのカタログにはどんな種類があるの?

  1. 生成に関するパターン(Creational patterns)
  2. 構造に関するパターン(Structural patterns)
  3. 振る舞いに関するパターン(Behavioral patterns)
  4. 並行処理制御に関するパターン(Concurrency patterns)

並行処理制御は初見だった。
他のは存在は知っていたけど、調べると興味深い。

そもそも、デザインパターンって何が美味しいんだ??って疑問が浮かぶので調べると・・・

  1. 初期の開発や、調査的な開発には先行事例としてお手本になって、開発効率が良い
  2. 後に大きな障害となる、軽微な問題(バグになりやすいコード)を発生しにくくする
  3. デザインパターンに精通している人には、コード自体の可読性が上がる
  4. ソフトウェア設計の堅牢性が増す
  5. 想定される仕様の追加や変更に柔軟に対応できる

言い過ぎてるような気もしたけど、納得はした。
(実際にデザインパターンは必ずしも最適解になるとは限らないと注釈があったし)

じゃあ、よく使いそうなもの、気になったものをピックアップしてPythonで書きます。
1回目は生成に関するパターンSingleton。一応、クラス図と説明の図を入れておく。

Singletonとは

唯一の存在を保証するためのパターン、このクラスのインスタンスはたった1つしか作らないし、作りたくないという時に使う。

つまり、ルールは
1. 指定したクラスのインスタンスが絶対に1個しか存在しないことを保証したい
2. インスタンスが1個しか存在しないことをプログラム上で表現したい
3. Singleton パターンは、コンストラクタを private(外部参照させない)
4. 同じ型のインスタンスが private なクラス変数として定義されている
5. 同じ型のインスタンスを返すクラス関数が定義されている(今回はget_name())

アプリケーションの全てのリクエストを通して常に一つである場合とか
、オブジェクト自体が大きいものやパフォーマンスコストが高いものをSingletonにすると良さそう。

singleton.py
class Singleton(object):
    __instance = None

    def __new__(cls, *args, **keys):
        if cls.__instance is None:
            cls.__instance = object.__new__(cls)
        return cls.__instance

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

    def get_name(self):
        return self.__name
main.py

def main():
    s1 = Singleton('aaa')
    s2 = Singleton('bbb')
    if s1 is s2:
        print(id(s1), id(s2))  #=> 4444873840 4444873840
        print(s1.get_name(), s2.get_name())  #=> bbb bbb
    else:
        print('s1 is NOT s2')

if __name__ == '__main__':
    main()

まとめ

一意のオブジェクトが出来た(同じインスタンスが得られている)

それは、1つのクラス定義に対して、クラスオブジェクトは1つだけ存在し、インスタンスオブジェクトは生成したインスタンスの数だけ存在するが、2回目の呼び出しでは、1回目の既存のインスタンスがあるので、新たにインスタンスを作ることを行っていないから。

Githubのrepositoryには入れてないけど、superを使っても出来るみたい。

my_singleton.py
class MySingleton():        
    _instance = None        

    def __new__(cls, *args, **kwargs):      
        if not cls._instance:       
            cls._instance = super(MySingleton, cls).__new__(cls, *args, **kwargs)       
        return cls._instance        

    def set_name(self, name):       
        self._name = name       
        return self._name       

    def get_name(self):     
        return self._name       
my_main.py
from my_singleton import MySingleton


if __name__ == "__main__":      
    my = MySingleton()      
    my2 = MySingleton()     
    my.set_name("a")        
    my2.set_name("b")       
    print(id(my), id(my2))  # => 3074264748 3074264748  
    print(my.get_name(), my2.get_name())  # => b b      
    print(my is my2)  # => True

でも、newを使うと既存オブジェクトを返してるけど、その度にinitが何度も呼ばれはずだから、あんまし効率良くないのかもしれない。(だからcallを使ってクラスのインスタンスを呼び出せるようにしている例もあるのかな)
Pythonのメタクラスも難しいなぁ。

参考