Exist Technote

[Python] Factory Method Pattern (팩토리 메서드 패턴) 본문

Design Pattern/Creational

[Python] Factory Method Pattern (팩토리 메서드 패턴)

by_Exist 2020. 9. 29. 02:25

Factory Method란?

  • GOF에서 설명하는 Factory Method는 다음과 같다.

    객체를 생성하기 위해 인터페이스를 정의하지만, 어떤 객체를 생성할 지에 대한 결정권을 서브클래스로 위임한다.

  • 즉, 특정 인터페이스의 구체적인 구현이 여러 개 존재하는 다양한 상황에서 사용될 수 있는 패턴.

핵심

  • ObjectFactory로 생성된 인스턴스의 동작을 눈여겨보자.

      class ObjectFactory:
          def __init__(self):
              self._builders = {}
    
          def register_builder(self, key, builder):
              self._builders[key] = builder
    
          def create(self, key, *args, **kwargs):
              builder = self._builders.get(key)
              if not builder:
                  raise ValueError(key)
              return builder(*args, **kwargs)
    • register_builder 메서드로 self._builders에 key와 콜러블 객체를 매핑할 수 있으며
    • create 메소드를 활용하여 key로 builder를 선택하고 인자들을 전달함으로써 객체를 생성하는 인터페이스를 보유하며, 객체를 생성할 지에 대한 결정권은 builder에게 위임되어 있다.

멋진 예제

  • music.py

      class MusicServiceProvider:
          def __init__(self):
              self._builders = {}
    
          def register_builder(self, service_id, builder):
              self._builders[key] = builder
    
          def get(self, service_id, *args, **kwargs):
              builder = self._builders.get(key)
              if not builder:
                  raise ValueError(key)
              return builder(*args, **kwargs)
    
      class MelonService:
          def __init__(self, consumer_key, consumer_secret):
              self._key = consumer_key
              self._secret = consumer_secret
    
          def connection(self):
              print(f'{self._key}와 {self._secret}로 멜론 서비스와 연결')
    
      class BugsService:
          def __init__(self, access_code):
              self._access_code = access_code
    
          def connection(self):
              print(f'{self._access_code}로 벅스 서비스와 연결')
    
      class LocalService:
          def __init__(self, location):
              self._location = location
    
          def connection(self):
              print(f'{self._location}로 로컬 서비스와 연결')
    
      class MelonServiceBuilder:
          def __init__(self):
              self._instance = None
    
          def __call__(self, melon_client_key, melon_client_secret, **_ignored):
              if not self._instance:
                  consumer_key, consumer_secret = \
                      self.authorize(melon_client_key, melon_client_secret)
                  self._instance = MelonService(consumer_key, consumer_secret)
              return self._instance
    
          def authorize(self, key, secret):
              return 'MELON_CONSUMER_KEY', 'MELON_CONSUMER_SECRET'
    
      class BugsServiceBuilder:
          def __init__(self):
              self._instance = None
    
          def __call__(self, bugs_client_key, bugs_client_secret, **_ignored):
              if not self._instance:
                  access_code = self.authorize(bugs_client_key, bugs_client_secret)
                  self._instance = BugsService(access_code)
              return self._instance
    
          def authorize(self, key, secret):
              return 'BUGS_ACCESS_CODE'
    
      def create_local_music_service(local_music_location, **_ignored):
          return LocalService(local_music_location)
    
      services = MusicServiceProvider()
      services.register_builder('MELON', MelonServiceBuilder())
      services.register_builder('BUGS', BugsServiceBuilder())
      services.register_builder('LOCAL', create_local_music_service)
  • program.py

      import music
    
      config = {
          'melon_client_key': 'THE_SPOTIFY_CLIENT_KEY',
          'melon_client_secret': 'THE_SPOTIFY_CLIENT_SECRET',
          'bugs_client_key': 'THE_BUGS_CLIENT_KEY',
          'bugs_client_secret': 'THE_BUGS_CLIENT_SECRET',
          'local_music_location': '/usr/data/music'
      }
    
      melon = music.services.get('MELON', **config)
      melon.connection()
      bugs = music.services.get('BUGS', **config)
      bugs.connection()
      local = music.services.get('LOCAL', **config)
      local.connection()
    
      melon2 = music.services.get('MELON', **config)
      print(f"melon is melon2 = {melon is melon2}")
      bugs2 = music.services.get("BUGS", **config)
      print(f"bugs is bugs2 = {bugs is bugs2}")

여담

  • 잘 만들어진 디자인 패턴은 레고 부품 하나하나로 구현된 성당처럼 느껴진다. 아름다울 정도이다.

참조

realpython.com

Comments