doc.dev1x.org

Simple Factory

1. 背景

オブジェクトを間接的に生成したい

2. 課題

オブジェクトを直接生成すると具体的なクラスとの結合が強くなってしまう

from abc import ABCMeta, abstractmethod

class Program(metaclass=ABCMeta):
    @abstractmethod
    def exec(self):
        pass

class Program01(Program):
    def exec(self):
        print('わーい!')

class Program02(Program):
    def exec(self):
        print('うぇーい!')

class Program03(Program):
    def exec(self):
        print('うわー!')

class Client:
    def run(self, type):
        if type == '01':
            p1 = Program01()  # Program01と依存関係が発生
            print(p1.exec())
        if type == '02':
            p2 = Program02()  # Program02と依存関係が発生
            print(p2.exec())
        if type == '03':
            p3 = Program03()  # Program03と依存関係が発生
            print(p3.exec())


if __name__=='__main__':
    c = Client()
    c.run('01')  # => わーい!
    c.run('02')  # => うぇーい!
    c.run('03')  # => うわー!

3. 解決策

オブジェクトの生成処理を専門に行うクラスを追加する

from abc import ABCMeta, abstractmethod

class Program(metaclass=ABCMeta):
    """ Programのインターフェイス """
    @abstractmethod
    def exec(self):
        pass

class Program01(Program):
    """ Programの実装1 """
    def exec(self):
        print('わーい!')

class Program02(Program):
    """ Programの実装2 """
    def exec(self):
        print('うぇーい!')

class Program03(Program):
    """ Programの実装3 """
    def exec(self):
        print('うわー!')

class Factory:
    """ ProgramのFactoryクラス """
    @staticmethod
    def create(type):
        if type == '01': return Program01()
        if type == '02': return Program02()
        if type == '03': return Program03()
        raise ValueError()

class Client:
    def run(self, type):
        p = Factory.create(type) # 依存関係にあるのはFactoryだけ & IF文も消えた
        print(p.exec())


if __name__=='__main__':
    c = Client()
    c.run('01')  # => わーい!
    c.run('02')  # => うぇーい!
    c.run('03')  # => うわー!

4. メリット

利用側のクラスは実装クラスのインターフェイスのみ知っていればよくなる

実装が増えても呼び出し元に影響しない

ユニットテストも容易になる

5. デメリット

構成要素が増える(=複雑度が上昇する)

6. 注意事項

if文の集約/隠蔽に積極的に使用する

関数として実装してもよい

# Programクラスの定義は省略

def factory(type):
    """ ProgramのFactory関数 """
    if type == '01': return Program01()
    if type == '02': return Program02()
    if type == '03': return Program03()
    raise ValueError()

if __name__=='__main__':
    p1 = factory('01')
    c1 = Client(p1)
    p2 = factory('02')
    c2 = Client(p2)
    p3 = factory('03')
    c3 = Client(p3)

    c1.run()  # => わーい!
    c2.run()  # => うぇーい!
    c3.run()  # => うわー!

参考資料

Appendix-1 Strategyパターンと併用したSimple Factoryパターン

from abc import ABCMeta, abstractmethod

class Program(metaclass=ABCMeta):
    """ Programのインターフェイス """
    @abstractmethod
    def exec(self):
        pass

class Program01(Program):
    """ Programの実装1 """
    def exec(self):
        print('わーい!')

class Program02(Program):
    """ Programの実装2 """
    def exec(self):
        print('うぇーい!')

class Program03(Program):
    """ Programの実装3 """
    def exec(self):
        print('うわー!')

class Factory:
    """ ProgramのFactoryクラス """
    @staticmethod
    def create(type):
        if type == '01': return Program01()
        if type == '02': return Program02()
        if type == '03': return Program03()
        raise ValueError()

class Client:
    def __init__(self, program):
        self.program = program

    def run(self):
        print(self.program.exec())


if __name__=='__main__':
    p1 = Factory.create('01')
    c1 = Client(p1)
    p2 = Factory.create('02')
    c2 = Client(p2)
    p3 = Factory.create('03')
    c3 = Client(p3)

    c1.run()  # => わーい!
    c2.run()  # => うぇーい!
    c3.run()  # => うわー!