Exist Technote

[Python] Abstract Factory Pattern (추상 팩토리 패턴) 본문

Design Pattern/Creational

[Python] Abstract Factory Pattern (추상 팩토리 패턴)

by_Exist 2020. 9. 26. 13:35

https://refactoring.guru/design-patterns/abstract-factory

참고

wikipedia
faif/python-patterns

예제

  • 코드를 직접 분석하고 나서 패턴을 이해하는 것이 옳은 순서라고 생각한다.

첫번째 예제 코드

import random

class PetShop:

    def __init__(self, animal_factory=None):
        self.pet_factory = animal_factory

    def show_pet(self):
        pet = self.pet_factory()
        print("We have a lovely {}".format(pet))
        print("It says {}".format(pet.speak()))

class Dog:
    def speak(self):
        return "woof"

    def __str__(self):
        return "Dog"

class Cat:
    def speak(self):
        return "meow"

    def __str__(self):
        return "Cat"

def random_animal():
    return random.choice([Dog, Cat])()

def main():
    cat_shop = PetShop(Cat)
    cat_shop.show_pet()

    shop = PetShop(random_animal)
    for i in range(3):
    shop.show_pet()
    print("=" * 20)

첫번째 예제 분석

  • PetShop은 생성자에 animal_factory라는 추상 팩토리를 인자로 받는다.

    class PetShop:
    
        def __init__(self, animal_factory=None):
        	# 추상 팩토리
            self.pet_factory = animal_factory    # animal_factory 클래스(추상 팩토리)가
    
        def show_pet(self):
            pet = self.pet_factory()    # 호출될 때 객체를 반환하고, 반환된 객체가
            print("We have a lovely {}".format(pet))    # __str__ 메서드를 지니기를,
            print("It says {}".format(pet.speak()))    # 또한 speak 메서드를 지니기를 기대한다.
  • 팩토리로 사용되는 객체는 총 세 가지이다.

      class Dog:
          def speak(self):
              return "woof"
    
          def __str__(self):
              return "Dog"
    
      class Cat:
          def speak(self):
              return "meow"
    
          def __str__(self):
              return "Cat"
    
      def random_animal():
          return random.choice([Dog, Cat])()
  • 추상 팩토리를 변경함으로써, 실제로 사용되는 객체군을 자유롭게 변경할 수 있다.

      def main():
          cat_shop = PetShop(Cat)
          cat_shop.show_pet()
    
          shop = PetShop(random_animal)
          for i in range(3):
          shop.show_pet()
          print("=" * 20)

두번째 예제 코드

import enum
from abc import *

class Button:
    @abstractmethod
    def Paint(self):
        pass

class WinButton(Button):
    def Paint(self):
        print ("Render a button in a Windows style")

class OSXButton(Button):
    def Paint(self):
        print ("Render a button in a Mac OSX style")

class MousePointer:
    @abstractmethod
    def Paint(self):
        pass

class WinMousePointer(MousePointer):
    def Paint(self):
        print("Render a mousepointer in a Windows style")

class OSXMousePointer(MousePointer):
    def Paint(self):
        print ("Render a mousepointer in a OSX style")

class GUIFactory:
    @abstractmethod
    def CreateButton(self):
        return Button

    @abstractmethod
    def CreateMousePointer(self):
        return MousePointer

class WinFactory(GUIFactory):
    def CreateButton(self):
        return WinButton()
    def CreateMousePointer(self):
        return WinMousePointer()

class OSXFactory(GUIFactory):
    def CreateButton(self):
        return OSXButton()
    def CreateMousePointer(self):
        return OSXMousePointer()

class Settings:
    @staticmethod
    def Default():
        return Appearance.WIN

class Appearance(enum.Enum):
    WIN = 0
    OSX = 1

def main():
    apperance = Settings.Default()
    if apperance == Appearance.WIN:
        factory = WinFactory()
    elif apperance == Appearance.OSX:
        factory = OSXFactory()
    button = factory.CreateButton()
    mousePointer = factory.CreateMousePointer()
    button.Paint()
    mousePointer.Paint()

if __name__ == '__main__':
    main()

두번째 예제 분석

  • 여러 os에서 사용되는 GUI를 구현하려 한다.

    • GUI는 버튼과 마우스포인터로 구성된다. (더 늘어날 수 있다.)

    • 윈도우용 GUI가 있고, 맥용 GUI가 있다. (OS는 더 늘어날 수 있다.)

      class Button:
          @abstractmethod
          def Paint(self):
              pass
      
      class WinButton(Button):
          def Paint(self):
              print ("Render a button in a Windows style")
      
      class OSXButton(Button):
          def Paint(self):
              print ("Render a button in a Mac OSX style")
      
      class MousePointer:
          @abstractmethod
          def Paint(self):
              pass
      
      class WinMousePointer(MousePointer):
          def Paint(self):
              print("Render a mousepointer in a Windows style")
      
      class OSXMousePointer(MousePointer):
          def Paint(self):
              print ("Render a mousepointer in a OSX style")
  • 각 OS에 해당하는 팩토리를 정의한다.

      class GUIFactory:
          @abstractmethod
          def CreateButton(self):
              return Button
    
          @abstractmethod
          def CreateMousePointer(self):
              return MousePointer
    
      class WinFactory(GUIFactory):
          def CreateButton(self):
              return WinButton()
          def CreateMousePointer(self):
              return WinMousePointer()
    
      class OSXFactory(GUIFactory):
          def CreateButton(self):
              return OSXButton()
          def CreateMousePointer(self):
              return OSXMousePointer()
  • enum을 활용하여 추상 팩토리를 간단하게 선택할 수 있도록 설계한다.

      class Settings:
          @staticmethod
          def Default():
              return Appearance.OSX
    
      class Appearance(enum.Enum):
          WIN = 0
          OSX = 1
  • 사용자는 자유롭게 GUI 그룹을 변경할 수 있다.

    def main():
        apperance = Settings.Default()
        if apperance == Appearance.WIN:
            factory = WinFactory()
        elif apperance == Appearance.OSX:
            factory = OSXFactory()
        button = factory.CreateButton()
        mousePointer = factory.CreateMousePointer()
        button.Paint()
        mousePointer.Paint()
    
    if __name__ == '__main__':
        main()

Abstract factory 패턴이란?

  • 사용 목적
    • 목적은 동일하지만 개념이 다른 팩토리들을 군으로 묶어 추상 팩토리로 구현하고, 추상 팩토리가 특정 인터페이스를 따른다고 가정하면 객체와 무관하게 시스템을 구현할 수 있게 된다.
    • 다양한 환경에서 동작하는 구조, 추상 팩토리가 추가되더라도 확장성 있는 구조를 설계할 수 있게 된다.
  • 잡담.
    • "객체를 생성하는 팩토리가 동일한 인터페이스를 따르며, 따라서 객체와는 별개로 시스템을 설계할 수 있는 패턴"이라고 정의할 수 있을 것 같다.
    • 첫 번째 예제와 두 번재 예제의 차이
      • 첫 번째 예제는 추상 팩토리가 객체의 군집화를 표현하지 못한다.
      • 두 번째 예제가 해당 군집화를 올바르게 표현하고 있다고 생각한다.
    • '파이썬 인 프렉티스' 책에서, 두 번째 예제와 비슷한 코드를 namespace 관련하여 더 파이써닉하게 구성하는 방법을 기술해놓았던 것으로 기억하는데, 어떤 방식인지 다시 살펴봐야 할 듯 하다.
Comments